- Added support for agent-driven assertions in the Asserter, allowing validation of responses using specified agents. - Introduced the `Use` and `Options` fields in the Assertion struct to facilitate agent configuration. - Enhanced the `evaluateAssertion` method to handle assertions of type "agent". - Implemented the `assertAgent` method to manage agent interactions and validation logic. - Updated `script_assert.go` to include the `assertAgentMethod` for JavaScript API integration. - Revised documentation in DESIGN_V2.md and TODO_V2.md to reflect the new agent-driven assertion capabilities and implementation status.
275 lines
6.6 KiB
Go
275 lines
6.6 KiB
Go
package test_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
v8 "github.com/yaoapp/gou/runtime/v8"
|
|
"github.com/yaoapp/yao/agent"
|
|
agenttest "github.com/yaoapp/yao/agent/test"
|
|
"github.com/yaoapp/yao/config"
|
|
"github.com/yaoapp/yao/test"
|
|
"rogchap.com/v8go"
|
|
)
|
|
|
|
func TestAsserter_AgentAssertion(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
|
|
// Load agent (includes assistants)
|
|
err := agent.Load(config.Conf)
|
|
if err != nil {
|
|
t.Fatalf("Failed to load agent: %v", err)
|
|
}
|
|
|
|
asserter := agenttest.NewAsserter()
|
|
|
|
tests := []struct {
|
|
name string
|
|
tc *agenttest.Case
|
|
output interface{}
|
|
expected bool
|
|
skipMsg string
|
|
}{
|
|
{
|
|
name: "agent assertion - pass",
|
|
tc: &agenttest.Case{
|
|
Assert: map[string]interface{}{
|
|
"type": "agent",
|
|
"use": "agents:tests.validator-agent",
|
|
"value": "Response should be a greeting",
|
|
},
|
|
},
|
|
output: "Hello! How can I help you today?",
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "agent assertion - fail",
|
|
tc: &agenttest.Case{
|
|
Assert: map[string]interface{}{
|
|
"type": "agent",
|
|
"use": "agents:tests.validator-agent",
|
|
"value": "Response should provide a detailed technical answer",
|
|
},
|
|
},
|
|
output: "I don't know.",
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "agent assertion - missing prefix",
|
|
tc: &agenttest.Case{
|
|
Assert: map[string]interface{}{
|
|
"type": "agent",
|
|
"use": "tests.validator-agent", // Missing agents: prefix
|
|
"value": "Should pass",
|
|
},
|
|
},
|
|
output: "Hello",
|
|
expected: false, // Should fail due to missing prefix
|
|
},
|
|
{
|
|
name: "agent assertion - with metadata",
|
|
tc: &agenttest.Case{
|
|
Assert: map[string]interface{}{
|
|
"type": "agent",
|
|
"use": "agents:tests.validator-agent",
|
|
"value": "Response is helpful",
|
|
"options": map[string]interface{}{
|
|
"metadata": map[string]interface{}{
|
|
"context": "customer support",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
output: "I'd be happy to help you with your order. Let me look that up for you.",
|
|
expected: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.skipMsg != "" {
|
|
t.Skip(tt.skipMsg)
|
|
}
|
|
|
|
passed, errMsg := asserter.Validate(tt.tc, tt.output)
|
|
if passed != tt.expected {
|
|
t.Errorf("Expected passed=%v, got passed=%v, error: %s", tt.expected, passed, errMsg)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAsserter_AgentAssertion_InvalidAgent(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
|
|
// Load agent (includes assistants)
|
|
err := agent.Load(config.Conf)
|
|
if err != nil {
|
|
t.Fatalf("Failed to load agent: %v", err)
|
|
}
|
|
|
|
asserter := agenttest.NewAsserter()
|
|
|
|
tc := &agenttest.Case{
|
|
Assert: map[string]interface{}{
|
|
"type": "agent",
|
|
"use": "agents:nonexistent.agent",
|
|
"value": "Should fail",
|
|
},
|
|
}
|
|
|
|
passed, errMsg := asserter.Validate(tc, "Hello")
|
|
assert.False(t, passed, "Should fail for nonexistent agent")
|
|
assert.Contains(t, errMsg, "failed to get validator agent", "Error should mention agent loading failure")
|
|
}
|
|
|
|
func TestAsserter_MapToAssertion_WithUseAndOptions(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
|
|
// Load agent (includes assistants)
|
|
err := agent.Load(config.Conf)
|
|
if err != nil {
|
|
t.Fatalf("Failed to load agent: %v", err)
|
|
}
|
|
|
|
asserter := agenttest.NewAsserter()
|
|
|
|
// Test that mapToAssertion correctly parses use and options fields
|
|
tc := &agenttest.Case{
|
|
Assert: map[string]interface{}{
|
|
"type": "agent",
|
|
"use": "agents:tests.validator-agent",
|
|
"value": "criteria here",
|
|
"options": map[string]interface{}{
|
|
"connector": "gpt-4o",
|
|
"metadata": map[string]interface{}{
|
|
"key": "value",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Validate triggers parseAssertions internally
|
|
// We just verify it doesn't panic and processes correctly
|
|
_, _ = asserter.Validate(tc, "test output")
|
|
// If we get here without panic, the parsing worked
|
|
}
|
|
|
|
// TestTestingT_AssertAgent tests the JSAPI t.assert.Agent() method
|
|
func TestTestingT_AssertAgent(t *testing.T) {
|
|
test.Prepare(t, config.Conf)
|
|
defer test.Clean()
|
|
|
|
// Load agent (includes assistants)
|
|
err := agent.Load(config.Conf)
|
|
if err != nil {
|
|
t.Fatalf("Failed to load agent: %v", err)
|
|
}
|
|
|
|
tests := []struct {
|
|
name string
|
|
script string
|
|
shouldFail bool
|
|
}{
|
|
{
|
|
name: "JSAPI agent assertion - pass",
|
|
script: `
|
|
function test(t) {
|
|
var response = "Hello! How can I help you today?";
|
|
t.assert.Agent(response, "tests.validator-agent", {
|
|
criteria: "Response should be a friendly greeting"
|
|
});
|
|
}
|
|
test(__test_t);
|
|
`,
|
|
shouldFail: false,
|
|
},
|
|
{
|
|
name: "JSAPI agent assertion - JSON response",
|
|
script: `
|
|
function test(t) {
|
|
var response = {
|
|
status: "success",
|
|
data: { user: "john", email: "john@example.com" },
|
|
message: "User created successfully"
|
|
};
|
|
t.assert.Agent(response, "tests.validator-agent", {
|
|
criteria: "Response should be a successful API response with user data"
|
|
});
|
|
}
|
|
test(__test_t);
|
|
`,
|
|
shouldFail: false,
|
|
},
|
|
{
|
|
name: "JSAPI agent assertion - with metadata",
|
|
script: `
|
|
function test(t) {
|
|
var response = "I'd be happy to help you with your order.";
|
|
t.assert.Agent(response, "tests.validator-agent", {
|
|
criteria: "Response is helpful and professional",
|
|
metadata: { context: "customer support" }
|
|
});
|
|
}
|
|
test(__test_t);
|
|
`,
|
|
shouldFail: false,
|
|
},
|
|
{
|
|
name: "JSAPI agent assertion - fail case",
|
|
script: `
|
|
function test(t) {
|
|
var response = "I don't know.";
|
|
t.assert.Agent(response, "tests.validator-agent", {
|
|
criteria: "Response should provide a detailed technical explanation"
|
|
});
|
|
}
|
|
test(__test_t);
|
|
`,
|
|
shouldFail: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Create TestingT
|
|
testingT := agenttest.NewTestingT(tt.name)
|
|
|
|
// Create V8 isolate and context
|
|
iso := v8go.NewIsolate()
|
|
defer iso.Dispose()
|
|
|
|
v8ctx := v8go.NewContext(iso)
|
|
defer v8ctx.Close()
|
|
|
|
// Create testing object
|
|
testObj, err := agenttest.NewTestingTObject(v8ctx, testingT)
|
|
if err != nil {
|
|
t.Fatalf("Failed to create testing object: %v", err)
|
|
}
|
|
|
|
// Set testing object as global
|
|
global := v8ctx.Global()
|
|
global.Set("__test_t", testObj)
|
|
|
|
// Run the test script
|
|
_, err = v8ctx.RunScript(tt.script, "test.js")
|
|
|
|
// Check results
|
|
if tt.shouldFail {
|
|
assert.True(t, testingT.Failed(), "Test should have failed")
|
|
} else {
|
|
if err != nil {
|
|
t.Errorf("Script execution error: %v", err)
|
|
}
|
|
assert.False(t, testingT.Failed(), "Test should have passed, errors: %v", testingT.Errors())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Ensure v8 is used (for script loading)
|
|
var _ = v8.Scripts
|