Files
ARTEX-chixuweihu/server/customtool_test.go
T
Autumn-27andClaude Opus 4.8 0ef8561411 chore: ARTEX 初始化
ARTEX — AI 自主渗透测试系统(Go 后端 + Next.js 前端,单二进制内嵌)。

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-26 04:27:45 -04:00

179 lines
5.3 KiB
Go

package server
import (
"context"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"os/exec"
"strings"
"testing"
"time"
)
func TestRenderTemplateCommand(t *testing.T) {
// command: string params shell-quoted; array → JSON (also quoted).
got := renderTemplate("nmap -p {ports} {target}", map[string]any{
"target": "10.0.0.1; rm -rf /", // injection attempt → must be single-quoted
"ports": []any{80.0, 443.0},
}, shellQuote)
if !strings.Contains(got, `'10.0.0.1; rm -rf /'`) {
t.Fatalf("target not shell-quoted: %q", got)
}
if strings.Contains(got, "rm -rf /'") && !strings.Contains(got, `'10.0.0.1; rm -rf /'`) {
t.Fatalf("possible injection leak: %q", got)
}
if strings.Contains(got, "{target}") || strings.Contains(got, "{ports}") {
t.Fatalf("placeholders not replaced: %q", got)
}
}
func TestRenderTemplateHTTP(t *testing.T) {
// http: identity (no shell quoting) — raw substitution into url/body.
got := renderTemplate("https://x/submit?flag={flag}", map[string]any{"flag": "CTF{abc}"}, identity)
if got != "https://x/submit?flag=CTF{abc}" {
t.Fatalf("http render: %q", got)
}
}
func TestScalarStr(t *testing.T) {
cases := []struct {
v any
want string
ok bool
}{
{"hi", "hi", true},
{true, "true", true},
{float64(80), "80", true}, // integer-valued float → no decimal
{float64(1.5), "1.5", true}, // real float
{[]any{1, 2}, "", false}, // array → not scalar
{map[string]any{}, "", false},
}
for _, c := range cases {
got, ok := scalarStr(c.v)
if ok != c.ok || (ok && got != c.want) {
t.Fatalf("scalarStr(%v) = (%q,%v), want (%q,%v)", c.v, got, ok, c.want, c.ok)
}
}
}
func TestEnsureSchema(t *testing.T) {
// empty → thin {args:string}
m := ensureSchema(nil)
props, _ := m["properties"].(map[string]any)
if _, ok := props["args"]; !ok {
t.Fatalf("empty schema should default to args: %v", m)
}
// non-empty → passthrough
raw := json.RawMessage(`{"type":"object","properties":{"target":{"type":"string"}}}`)
m2 := ensureSchema(raw)
p2, _ := m2["properties"].(map[string]any)
if _, ok := p2["target"]; !ok {
t.Fatalf("non-empty schema should pass through: %v", m2)
}
}
func TestShellQuote(t *testing.T) {
if got := shellQuote("a'b"); got != `'a'\''b'` {
t.Fatalf("shellQuote(a'b) = %q", got)
}
}
// TestExecPython runs a real Python script end-to-end: it must read params from
// stdin JSON and the mirrored env var, then print — verifying the whole script
// param-passing path. Skips if no python3.
func TestExecPython(t *testing.T) {
interp, err := exec.LookPath("python3")
if err != nil {
t.Skip("python3 unavailable")
}
code := `import json,sys,os
a = json.load(sys.stdin)
print("stdin_target=" + a["target"])
print("env_target=" + os.environ.get("TOOL_TARGET",""))
print("port=" + str(a["port"]))
`
out, err := execPython(context.Background(), interp, "test", code,
map[string]any{"target": "example.com", "port": float64(8080)},
t.TempDir(), nil, 10*time.Second)
if err != nil {
t.Fatalf("execPython error: %v", err)
}
for _, want := range []string{"stdin_target=example.com", "env_target=example.com", "port=8080"} {
if !strings.Contains(out, want) {
t.Fatalf("output missing %q:\n%s", want, out)
}
}
}
// TestRunHTTPTool functionally exercises the http executor against a local server:
// method/url/header/body templates are rendered from params, the request is sent,
// and the response status+body are returned. No recording proxy → s.m untouched.
func TestRunHTTPTool(t *testing.T) {
var gotMethod, gotAuth, gotBody string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotMethod = r.Method
gotAuth = r.Header.Get("Authorization")
b, _ := io.ReadAll(r.Body)
gotBody = string(b)
w.WriteHeader(201)
_, _ = w.Write([]byte(`{"ok":true}`))
}))
defer srv.Close()
execRaw, _ := json.Marshal(map[string]any{
"method": "POST",
"url": srv.URL + "/submit?flag={flag}",
"headers": map[string]string{"Authorization": "Bearer {token}"},
"body": `{"flag":"{flag}"}`,
})
res, err := (&Server{}).runHTTPTool(context.Background(), execRaw,
map[string]any{"flag": "CTF{x}", "token": "sekret"})
if err != nil {
t.Fatalf("runHTTPTool: %v", err)
}
if res.IsError || len(res.Content) == 0 {
t.Fatalf("unexpected result: %+v", res)
}
var out struct {
Status int `json:"status"`
Body string `json:"body"`
}
_ = json.Unmarshal([]byte(res.Content[0].Text), &out)
if gotMethod != "POST" {
t.Fatalf("method: %q", gotMethod)
}
if gotAuth != "Bearer sekret" {
t.Fatalf("header template not rendered: %q", gotAuth)
}
if gotBody != `{"flag":"CTF{x}"}` {
t.Fatalf("body template not rendered: %q", gotBody)
}
if out.Status != 201 || !strings.Contains(out.Body, `"ok":true`) {
t.Fatalf("response not captured: %+v", out)
}
}
func TestDetectPython(t *testing.T) {
if _, err := exec.LookPath("python3"); err != nil {
t.Skip("python3 unavailable")
}
if p := detectPython(); p == "" {
t.Fatal("detectPython returned empty despite python3 on PATH")
}
}
func TestClipOutput(t *testing.T) {
long := strings.Repeat("x", 7000)
got := clipOutput(long, nil)
if len(got) >= 7000 || !strings.Contains(got, "截断") {
t.Fatalf("clipOutput should truncate long output, got len %d", len(got))
}
short := "ok"
if clipOutput(short, nil) != short {
t.Fatal("clipOutput should pass short output through")
}
}