Enhance context handling in MCP tests and JS API

- Added tests to verify the presence and correctness of authorized and metadata fields in MCP tool context passing.
- Updated MCP tool context tests to include user and tenant ID assertions, ensuring accurate context validation.
- Enhanced JS API to handle authorized and metadata fields, including tests for nil scenarios, improving robustness and clarity in context management.
This commit is contained in:
Max 2025-12-05 20:20:21 +08:00
parent a66c2c7666
commit 53a22fd9e4
3 changed files with 200 additions and 12 deletions

View file

@ -302,9 +302,24 @@ func TestMCPToolContextPassing(t *testing.T) {
assert.NotEmpty(t, assistantID, "assistant_id should have a value")
assert.Equal(t, "test-assistant-mcptest", assistantID, "assistant_id should match")
// Verify authorized information
authorizedData, ok := contextData["authorized"].(map[string]interface{})
assert.True(t, ok, "Context should have authorized field")
assert.NotNil(t, authorizedData, "Authorized data should not be nil")
userID, ok := authorizedData["user_id"].(string)
assert.True(t, ok, "Authorized should have user_id field")
assert.Equal(t, "test-user-123", userID, "User ID should match")
tenantID, ok := authorizedData["tenant_id"].(string)
assert.True(t, ok, "Authorized should have tenant_id field")
assert.Equal(t, "test-tenant-456", tenantID, "Tenant ID should match")
t.Logf("✓ Context successfully passed to MCP tool")
t.Logf(" - ChatID: %s", chatID)
t.Logf(" - AssistantID: %s", assistantID)
t.Logf(" - UserID: %s", userID)
t.Logf(" - TenantID: %s", tenantID)
}
// TestMCPToolContextPassingParallel tests that agent context is correctly passed in parallel calls
@ -372,6 +387,13 @@ func TestMCPToolContextPassingParallel(t *testing.T) {
assert.True(t, ok, "Context %d should have chat_id field", i)
assert.Equal(t, "parallel-chat-789", chatID, "Chat ID in result %d should match", i)
// Verify authorized information in parallel call
authorizedData, ok := contextData["authorized"].(map[string]interface{})
assert.True(t, ok, "Context %d should have authorized field", i)
if userID, ok := authorizedData["user_id"].(string); ok {
assert.Equal(t, "parallel-user-123", userID, "User ID in result %d should match", i)
}
t.Logf("✓ Result %d successfully received context", i)
}

View file

@ -102,22 +102,32 @@ func (ctx *Context) NewObject(v8ctx *v8go.Context) (*v8go.Value, error) {
clientVal.Release() // Release Go-side Persistent handle, V8 internal reference remains
}
// Metadata object
if ctx.Metadata != nil {
metadataVal, err := bridge.JsValue(v8ctx, ctx.Metadata)
if err == nil {
obj.Set("metadata", metadataVal)
metadataVal.Release() // Release Go-side Persistent handle, V8 internal reference remains
}
// Metadata object - always set to empty map if nil
metadataData := ctx.Metadata
if metadataData == nil {
metadataData = map[string]interface{}{}
}
metadataVal, err := bridge.JsValue(v8ctx, metadataData)
if err == nil {
obj.Set("metadata", metadataVal)
metadataVal.Release() // Release Go-side Persistent handle, V8 internal reference remains
}
// Authorized object
// Authorized object - set individual fields to ensure proper structure
var authorizedData map[string]interface{}
if ctx.Authorized != nil {
authorizedVal, err := bridge.JsValue(v8ctx, ctx.Authorized)
if err == nil {
obj.Set("authorized", authorizedVal)
authorizedVal.Release() // Release Go-side Persistent handle, V8 internal reference remains
authorizedData = map[string]interface{}{
"user_id": ctx.Authorized.UserID,
"tenant_id": ctx.Authorized.TenantID,
"client_id": ctx.Authorized.ClientID,
}
} else {
authorizedData = map[string]interface{}{}
}
authorizedVal, err := bridge.JsValue(v8ctx, authorizedData)
if err == nil {
obj.Set("authorized", authorizedVal)
authorizedVal.Release() // Release Go-side Persistent handle, V8 internal reference remains
}
return instance.Value, nil

View file

@ -463,3 +463,159 @@ func TestJsValueTrace(t *testing.T) {
assert.NotEmpty(t, result["node_id"], "node_id should not be empty")
assert.Equal(t, true, result["success"], "operation should succeed")
}
// TestJsValueAuthorizedAndMetadata test the authorized and metadata fields
func TestJsValueAuthorizedAndMetadata(t *testing.T) {
test.Prepare(t, config.Conf)
defer test.Clean()
cxt := &context.Context{
ChatID: "test-chat-id",
AssistantID: "test-assistant-id",
Context: stdContext.Background(),
IDGenerator: message.NewIDGenerator(),
Authorized: &types.AuthorizedInfo{
UserID: "user-123",
TenantID: "tenant-456",
ClientID: "client-789",
},
Metadata: map[string]interface{}{
"request_id": "req-001",
"source": "api",
"version": "1.0.0",
},
}
v8.RegisterFunction("testAuthorizedMetadata", testAuthorizedMetadataEmbed)
res, err := v8.Call(v8.CallOptions{}, `
function test(cxt) {
return testAuthorizedMetadata(cxt)
}`, cxt)
if err != nil {
t.Fatalf("Call failed: %v", err)
}
result, ok := res.(map[string]interface{})
if !ok {
t.Fatalf("Expected map result, got %T", res)
}
// Verify authorized object
authorized, ok := result["authorized"].(map[string]interface{})
assert.True(t, ok, "authorized should be an object")
assert.Equal(t, "user-123", authorized["user_id"], "authorized.user_id mismatch")
assert.Equal(t, "tenant-456", authorized["tenant_id"], "authorized.tenant_id mismatch")
assert.Equal(t, "client-789", authorized["client_id"], "authorized.client_id mismatch")
// Verify metadata object
metadata, ok := result["metadata"].(map[string]interface{})
assert.True(t, ok, "metadata should be an object")
assert.Equal(t, "req-001", metadata["request_id"], "metadata.request_id mismatch")
assert.Equal(t, "api", metadata["source"], "metadata.source mismatch")
assert.Equal(t, "1.0.0", metadata["version"], "metadata.version mismatch")
}
func testAuthorizedMetadataEmbed(iso *v8go.Isolate) *v8go.FunctionTemplate {
return v8go.NewFunctionTemplate(iso, testAuthorizedMetadataFunction)
}
func testAuthorizedMetadataFunction(info *v8go.FunctionCallbackInfo) *v8go.Value {
var args = info.Args()
if len(args) < 1 {
return bridge.JsException(info.Context(), "Missing parameters")
}
ctx, err := args[0].AsObject()
if err != nil {
return bridge.JsException(info.Context(), err)
}
// Extract authorized and metadata fields
result := map[string]interface{}{}
// Get authorized
authorizedVal, err := ctx.Get("authorized")
if err != nil {
return bridge.JsException(info.Context(), err)
}
if !authorizedVal.IsUndefined() && !authorizedVal.IsNull() {
authorized, err := bridge.GoValue(authorizedVal, info.Context())
if err != nil {
return bridge.JsException(info.Context(), err)
}
result["authorized"] = authorized
}
// Get metadata
metadataVal, err := ctx.Get("metadata")
if err != nil {
return bridge.JsException(info.Context(), err)
}
if !metadataVal.IsUndefined() && !metadataVal.IsNull() {
metadata, err := bridge.GoValue(metadataVal, info.Context())
if err != nil {
return bridge.JsException(info.Context(), err)
}
result["metadata"] = metadata
}
jsVal, err := bridge.JsValue(info.Context(), result)
if err != nil {
return bridge.JsException(info.Context(), err)
}
return jsVal
}
// TestJsValueAuthorizedNil test when authorized is nil
func TestJsValueAuthorizedNil(t *testing.T) {
test.Prepare(t, config.Conf)
defer test.Clean()
cxt := &context.Context{
ChatID: "test-chat-id",
AssistantID: "test-assistant-id",
Context: stdContext.Background(),
IDGenerator: message.NewIDGenerator(),
Authorized: nil, // Explicitly nil
Metadata: nil, // Explicitly nil (should be empty object)
}
res, err := v8.Call(v8.CallOptions{}, `
function test(cxt) {
// Debug: check the actual values
const authorized = cxt.authorized;
const metadata = cxt.metadata;
return {
authorized_type: typeof authorized,
authorized_is_null: authorized === null,
authorized_is_undefined: authorized === undefined,
metadata_type: typeof metadata,
metadata_is_object: typeof metadata === 'object' && metadata !== null,
metadata_is_empty: metadata && Object.keys(metadata).length === 0,
has_authorized: 'authorized' in cxt,
has_metadata: 'metadata' in cxt
}
}`, cxt)
if err != nil {
t.Fatalf("Call failed: %v", err)
}
result, ok := res.(map[string]interface{})
if !ok {
t.Fatalf("Expected map result, got %T", res)
}
// Verify authorized exists and is an empty object when nil
assert.Equal(t, true, result["has_authorized"], "authorized property should exist")
assert.Equal(t, "object", result["authorized_type"], "authorized should be an object")
assert.Equal(t, true, result["metadata_is_object"], "authorized should be an object (not null)")
// Verify metadata is an empty object when not set
assert.Equal(t, true, result["has_metadata"], "metadata property should exist")
assert.Equal(t, "object", result["metadata_type"], "metadata should be an object")
assert.Equal(t, true, result["metadata_is_object"], "metadata should be an object")
assert.Equal(t, true, result["metadata_is_empty"], "metadata should be empty object when not set")
}