feat(tools/basic): 内置基础工具(calculator(注入安全)/datetime/random/text/json/markdown)+ 批量注册
This commit is contained in:
1 parent
b3c61aa1ff
commit
08223c06a5
19 files changed
+2074
No files matched your search
@@ -0,0 +1,113 @@
|
||||
package basic
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestCalculator_Evaluate_Normal covers the arithmetic grammar.
|
||||
func TestCalculator_Evaluate_Normal(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := calculator{}
|
||||
cases := []struct {
|
||||
expr string
|
||||
want float64
|
||||
}{
|
||||
{expr: "1+2*3", want: 7},
|
||||
{expr: "(1+2)*3", want: 9},
|
||||
{expr: "2^10", want: 1024},
|
||||
{expr: "2^3^2", want: 512}, // right-associative
|
||||
{expr: "-2^2", want: -4}, // power binds tighter than unary minus
|
||||
{expr: "2^-3", want: 0.125}, // negative exponent
|
||||
{expr: "sqrt(16)", want: 4},
|
||||
{expr: "sqrt(2)+1", want: 2.414213562373095},
|
||||
{expr: "10/4", want: 2.5},
|
||||
{expr: " ( 5 + 3 ) * 2 ", want: 16},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(tc.expr, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got, err := run(t, c, map[string]any{"expression": tc.expr})
|
||||
assertNoErr(t, err)
|
||||
assertApprox(t, fieldFloat(t, mapResult(t, got), "result"), tc.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestCalculator_Evaluate_Errors covers invalid expressions and missing/typed args.
|
||||
func TestCalculator_Evaluate_Errors(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := calculator{}
|
||||
byExpression := []struct {
|
||||
name string
|
||||
args map[string]any
|
||||
}{
|
||||
{name: "missing expression", args: map[string]any{}},
|
||||
{name: "non-string expression", args: map[string]any{"expression": 42}},
|
||||
{name: "division by zero", args: map[string]any{"expression": "1/0"}},
|
||||
{name: "trailing operator", args: map[string]any{"expression": "1+"}},
|
||||
{name: "bare identifier", args: map[string]any{"expression": "x+1"}},
|
||||
{name: "unknown function", args: map[string]any{"expression": "sqrt(16"}},
|
||||
{name: "negative sqrt", args: map[string]any{"expression": "sqrt(-1)"}},
|
||||
{name: "empty string", args: map[string]any{"expression": ""}},
|
||||
{name: "non-ASCII injection", args: map[string]any{"expression": "1+2;DROP"}},
|
||||
}
|
||||
for _, tc := range byExpression {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := run(t, c, tc.args)
|
||||
if err == nil {
|
||||
t.Fatalf("want error, got nil")
|
||||
}
|
||||
if !errors.Is(err, ErrInvalidArgument) && !errors.Is(err, ErrEvaluation) {
|
||||
t.Fatalf("err %v wraps neither ErrInvalidArgument nor ErrEvaluation", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestCalculator_InjectionRejected ensures expression injection is refused.
|
||||
func TestCalculator_InjectionRejected(t *testing.T) {
|
||||
t.Parallel()
|
||||
c := calculator{}
|
||||
payloads := []string{
|
||||
"os.system('id')",
|
||||
"process.env.HOME",
|
||||
`__import__("os")`,
|
||||
"1+2*3;rm -rf /",
|
||||
"`id`",
|
||||
"eval(1)",
|
||||
"1 && 2",
|
||||
"len(open('x'))",
|
||||
}
|
||||
for _, p := range payloads {
|
||||
p := p
|
||||
t.Run(p, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := run(t, c, map[string]any{"expression": p})
|
||||
assertIs(t, err, ErrEvaluation)
|
||||
})
|
||||
}
|
||||
// The expression length bound rejects a long payload.
|
||||
large := strings.Repeat("1+", 200) + "1"
|
||||
_, err := run(t, c, map[string]any{"expression": large})
|
||||
assertIs(t, err, ErrEvaluation)
|
||||
}
|
||||
|
||||
// TestCalculator_Schema verifies the JSON Schema contract.
|
||||
func TestCalculator_Schema(t *testing.T) {
|
||||
t.Parallel()
|
||||
p, err := calculator{}.Parameters()
|
||||
assertNoErr(t, err)
|
||||
reqs := p["required"].([]any)
|
||||
if len(reqs) != 1 || reqs[0] != "expression" {
|
||||
t.Fatalf("required = %v, want [expression]", reqs)
|
||||
}
|
||||
props := p["properties"].(map[string]any)
|
||||
if got := props["expression"].(map[string]any)["type"]; got != "string" {
|
||||
t.Fatalf("expression type = %v, want string", got)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user