Files
higress/plugins/wasm-go/extensions/ai-json-resp/main_test.go

893 lines
26 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright (c) 2024 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"encoding/json"
"testing"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
"github.com/higress-group/wasm-go/pkg/test"
"github.com/santhosh-tekuri/jsonschema"
"github.com/stretchr/testify/require"
)
// 测试配置:基础配置
var basicConfig = func() json.RawMessage {
data, _ := json.Marshal(map[string]interface{}{
"serviceName": "ai-service",
"serviceDomain": "api.openai.com",
"servicePort": 443,
"servicePath": "/v1/chat/completions",
"apiKey": "sk-test123",
"serviceTimeout": 30000,
"maxRetry": 3,
"contentPath": "choices.0.message.content",
"enableContentDisposition": true,
// 添加一个简单的JSON Schema避免编译失败
"jsonSchema": map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"content": map[string]interface{}{
"type": "string",
},
},
},
})
return data
}()
// 测试配置使用serviceUrl的配置
var serviceUrlConfig = func() json.RawMessage {
data, _ := json.Marshal(map[string]interface{}{
"serviceName": "ai-service",
"serviceUrl": "https://api.openai.com/v1/chat/completions",
"apiKey": "sk-test456",
"serviceTimeout": 50000,
"maxRetry": 5,
"contentPath": "choices.0.message.content",
"enableContentDisposition": false,
// 添加一个简单的JSON Schema避免编译失败
"jsonSchema": map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"content": map[string]interface{}{
"type": "string",
},
},
},
})
return data
}()
// 测试配置包含JSON Schema的配置
var jsonSchemaConfig = func() json.RawMessage {
data, _ := json.Marshal(map[string]interface{}{
"serviceName": "ai-service",
"serviceDomain": "api.openai.com",
"servicePort": 443,
"apiKey": "sk-test789",
"jsonSchema": map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"name": map[string]interface{}{
"type": "string",
},
"age": map[string]interface{}{
"type": "integer",
},
},
"required": []string{"name"},
},
"enableSwagger": true,
"enableOas3": false,
})
return data
}()
// 测试配置启用OAS3的配置
var oas3Config = func() json.RawMessage {
data, _ := json.Marshal(map[string]interface{}{
"serviceName": "ai-service",
"serviceDomain": "api.openai.com",
"servicePort": 443,
"apiKey": "sk-test101",
"jsonSchema": map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"title": map[string]interface{}{
"type": "string",
},
"content": map[string]interface{}{
"type": "string",
},
},
},
"enableSwagger": false,
"enableOas3": true,
})
return data
}()
// 测试配置无效的JSON Schema配置
var invalidJsonSchemaConfig = func() json.RawMessage {
data, _ := json.Marshal(map[string]interface{}{
"serviceName": "ai-service",
"serviceDomain": "api.openai.com",
"servicePort": 443,
"apiKey": "sk-test303",
"jsonSchema": "invalid-schema",
})
return data
}()
// 测试配置:缺少必需字段的配置
var missingRequiredConfig = func() json.RawMessage {
data, _ := json.Marshal(map[string]interface{}{
"apiKey": "sk-test404",
"serviceTimeout": 30000,
})
return data
}()
func TestParseConfig(t *testing.T) {
test.RunGoTest(t, func(t *testing.T) {
// 测试基础配置解析
t.Run("basic config", func(t *testing.T) {
host, status := test.NewTestHost(basicConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
config, err := host.GetMatchConfig()
require.NoError(t, err)
require.NotNil(t, config)
pluginConfig := config.(*PluginConfig)
require.Equal(t, "ai-service", pluginConfig.serviceName)
require.Equal(t, "api.openai.com", pluginConfig.serviceDomain)
require.Equal(t, 443, pluginConfig.servicePort)
require.Equal(t, "/v1/chat/completions", pluginConfig.servicePath)
require.Equal(t, "sk-test123", pluginConfig.apiKey)
require.Equal(t, 30000, pluginConfig.serviceTimeout)
require.Equal(t, 3, pluginConfig.maxRetry)
require.Equal(t, "choices.0.message.content", pluginConfig.contentPath)
require.True(t, pluginConfig.enableContentDisposition)
require.NotNil(t, pluginConfig.jsonSchema)
require.Equal(t, jsonschema.Draft7, pluginConfig.draft)
require.True(t, pluginConfig.enableJsonSchemaValidation)
})
// 测试使用serviceUrl的配置解析
t.Run("serviceUrl config", func(t *testing.T) {
host, status := test.NewTestHost(serviceUrlConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
config, err := host.GetMatchConfig()
require.NoError(t, err)
require.NotNil(t, config)
pluginConfig := config.(*PluginConfig)
require.Equal(t, "ai-service", pluginConfig.serviceName)
require.Equal(t, "api.openai.com", pluginConfig.serviceDomain)
require.Equal(t, 443, pluginConfig.servicePort)
require.Equal(t, "/v1/chat/completions", pluginConfig.servicePath)
require.Equal(t, "sk-test456", pluginConfig.apiKey)
require.Equal(t, 50000, pluginConfig.serviceTimeout)
require.Equal(t, 5, pluginConfig.maxRetry)
require.False(t, pluginConfig.enableContentDisposition)
})
// 测试包含JSON Schema的配置解析
t.Run("jsonSchema config", func(t *testing.T) {
host, status := test.NewTestHost(jsonSchemaConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
config, err := host.GetMatchConfig()
require.NoError(t, err)
require.NotNil(t, config)
pluginConfig := config.(*PluginConfig)
require.NotNil(t, pluginConfig.jsonSchema)
require.Equal(t, jsonschema.Draft4, pluginConfig.draft)
require.True(t, pluginConfig.enableJsonSchemaValidation)
require.NotNil(t, pluginConfig.compile)
})
// 测试启用OAS3的配置解析
t.Run("oas3 config", func(t *testing.T) {
host, status := test.NewTestHost(oas3Config)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
config, err := host.GetMatchConfig()
require.NoError(t, err)
require.NotNil(t, config)
pluginConfig := config.(*PluginConfig)
require.Equal(t, jsonschema.Draft7, pluginConfig.draft)
require.True(t, pluginConfig.enableJsonSchemaValidation)
})
// 测试无效的JSON Schema配置
t.Run("invalid jsonSchema config", func(t *testing.T) {
host, status := test.NewTestHost(invalidJsonSchemaConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
config, err := host.GetMatchConfig()
require.NoError(t, err)
require.NotNil(t, config)
pluginConfig := config.(*PluginConfig)
// 根据插件的实际行为无效的JSON Schema会导致编译失败
require.Equal(t, uint32(JSON_SCHEMA_COMPILE_FAILED_CODE), pluginConfig.rejectStruct.RejectCode)
})
// 测试缺少必需字段的配置
t.Run("missing required config", func(t *testing.T) {
host, status := test.NewTestHost(missingRequiredConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
config, _ := host.GetMatchConfig()
require.NotNil(t, config)
pluginConfig := config.(*PluginConfig)
// 根据插件的实际行为缺少serviceDomain会导致JSON Schema编译失败
require.Equal(t, uint32(JSON_SCHEMA_COMPILE_FAILED_CODE), pluginConfig.rejectStruct.RejectCode)
require.Contains(t, pluginConfig.rejectStruct.RejectMsg, "Json Schema compile failed")
})
})
}
func TestOnHttpRequestHeaders(t *testing.T) {
test.RunTest(t, func(t *testing.T) {
// 测试正常请求头处理
t.Run("normal request headers", func(t *testing.T) {
host, status := test.NewTestHost(basicConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
// 设置请求头
action := host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/v1/chat/completions"},
{":method", "POST"},
{"Authorization", "Bearer sk-user123"},
{"Content-Type", "application/json"},
{"Content-Length", "100"},
})
// 应该返回ActionContinue
require.Equal(t, types.ActionContinue, action)
})
// 测试来自插件的请求头处理
t.Run("request from this plugin", func(t *testing.T) {
host, status := test.NewTestHost(basicConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
// 设置来自插件的请求头
action := host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/v1/chat/completions"},
{":method", "POST"},
{EXTEND_HEADER_KEY, "true"},
{"Content-Type", "application/json"},
})
// 应该返回ActionContinue
require.Equal(t, types.ActionContinue, action)
})
// 测试没有Authorization头的请求
t.Run("no authorization header", func(t *testing.T) {
host, status := test.NewTestHost(basicConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
// 设置没有Authorization的请求头
action := host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/v1/chat/completions"},
{":method", "POST"},
{"Content-Type", "application/json"},
{"Content-Length", "100"},
})
// 应该返回ActionContinue
require.Equal(t, types.ActionContinue, action)
})
// 测试配置错误的请求头处理
t.Run("config error", func(t *testing.T) {
host, status := test.NewTestHost(missingRequiredConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
// 设置请求头
action := host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/v1/chat/completions"},
{":method", "POST"},
{"Content-Type", "application/json"},
})
// 应该返回ActionPause
require.Equal(t, types.ActionPause, action)
})
})
}
func TestOnHttpRequestBody(t *testing.T) {
test.RunTest(t, func(t *testing.T) {
// 测试来自插件的请求(应该直接继续)
t.Run("request from this plugin", func(t *testing.T) {
host, status := test.NewTestHost(basicConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
// 设置请求头包含EXTEND_HEADER_KEY来标记请求来自插件
host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/v1/chat/completions"},
{":method", "POST"},
{"Content-Type", "application/json"},
{EXTEND_HEADER_KEY, "true"},
})
// 设置请求体
body := `{
"model": "gpt-3.5-turbo",
"messages": [
{"role": "user", "content": "Hello"}
]
}`
action := host.CallOnHttpRequestBody([]byte(body))
// 应该返回ActionContinue因为请求来自插件
require.Equal(t, types.ActionContinue, action)
})
// 测试配置错误的请求体处理
t.Run("config error", func(t *testing.T) {
host, status := test.NewTestHost(missingRequiredConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
// 设置请求头
host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/v1/chat/completions"},
{":method", "POST"},
{"Content-Type", "application/json"},
})
// 设置请求体
body := `{
"model": "gpt-3.5-turbo",
"messages": [
{"role": "user", "content": "Hello"}
]
}`
action := host.CallOnHttpRequestBody([]byte(body))
// 应该返回ActionContinue因为配置有错误
require.Equal(t, types.ActionContinue, action)
})
// 测试正常请求体处理 - 成功响应
t.Run("normal request with successful response", func(t *testing.T) {
host, status := test.NewTestHost(basicConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
// 设置请求头
host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/v1/chat/completions"},
{":method", "POST"},
{"Content-Type", "application/json"},
})
// 设置请求体
body := `{
"model": "gpt-3.5-turbo",
"messages": [
{"role": "user", "content": "What is AI?"}
]
}`
action := host.CallOnHttpRequestBody([]byte(body))
// 应该返回ActionPause等待外部服务响应
require.Equal(t, types.ActionPause, action)
// 模拟外部服务返回成功响应
host.CallOnHttpCall([][2]string{
{":status", "200"},
{"content-type", "application/json"},
}, []byte(`{
"id": "chatcmpl-123",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "{\"definition\": \"AI is artificial intelligence\", \"examples\": [\"machine learning\", \"natural language processing\"]}"
}
}
]
}`))
response := host.GetLocalResponse()
require.NotNil(t, response)
require.Contains(t, string(response.Data), "definition")
require.Contains(t, string(response.Data), "examples")
// 完成HTTP请求
host.CompleteHttp()
})
// 测试正常请求体处理 - 需要重试的响应
t.Run("normal request with retry response", func(t *testing.T) {
host, status := test.NewTestHost(basicConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
// 设置请求头
host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/v1/chat/completions"},
{":method", "POST"},
{"Content-Type", "application/json"},
})
// 设置请求体
body := `{
"model": "gpt-3.5-turbo",
"messages": [
{"role": "user", "content": "What is AI?"}
]
}`
action := host.CallOnHttpRequestBody([]byte(body))
// 应该返回ActionPause等待外部服务响应
require.Equal(t, types.ActionPause, action)
// 模拟外部服务返回需要重试的响应content字段不是有效JSON
host.CallOnHttpCall([][2]string{
{":status", "200"},
{"content-type", "application/json"},
}, []byte(`{
"id": "chatcmpl-123",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "AI is artificial intelligence. It includes machine learning and natural language processing."
}
}
]
}`))
// 由于content不是有效JSON插件会进行重试
// 模拟重试请求的响应
host.CallOnHttpCall([][2]string{
{":status", "200"},
{"content-type", "application/json"},
}, []byte(`{
"id": "chatcmpl-456",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "{\"definition\": \"AI is artificial intelligence\", \"examples\": [\"machine learning\", \"natural language processing\"]}"
}
}
]
}`))
// 验证最终响应体是提取的JSON内容
response := host.GetLocalResponse()
require.NotNil(t, response)
require.Contains(t, string(response.Data), "definition")
require.Contains(t, string(response.Data), "examples")
// 完成HTTP请求
host.CompleteHttp()
})
// 测试外部服务返回无效响应体
t.Run("external service returns invalid response body", func(t *testing.T) {
host, status := test.NewTestHost(basicConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
// 设置请求头
host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/v1/chat/completions"},
{":method", "POST"},
{"Content-Type", "application/json"},
})
// 设置请求体
body := `{
"model": "gpt-3.5-turbo",
"messages": [
{"role": "user", "content": "What is AI?"}
]
}`
action := host.CallOnHttpRequestBody([]byte(body))
// 应该返回ActionPause等待外部服务响应
require.Equal(t, types.ActionPause, action)
// 模拟外部服务返回无效的响应体
host.CallOnHttpCall([][2]string{
{":status", "200"},
{"content-type", "application/json"},
}, []byte(`invalid json response`))
// 验证响应体包含错误信息
response := host.GetLocalResponse()
require.NotNil(t, response)
require.Contains(t, string(response.Data), "invalid json response")
// 完成HTTP请求
host.CompleteHttp()
})
// 测试外部服务返回缺少content字段的响应
t.Run("external service returns response without content field", func(t *testing.T) {
host, status := test.NewTestHost(basicConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
// 设置请求头
host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/v1/chat/completions"},
{":method", "POST"},
{"Content-Type", "application/json"},
})
// 设置请求体
body := `{
"model": "gpt-3.5-turbo",
"messages": [
{"role": "user", "content": "What is AI?"}
]
}`
action := host.CallOnHttpRequestBody([]byte(body))
// 应该返回ActionPause等待外部服务响应
require.Equal(t, types.ActionPause, action)
// 模拟外部服务返回缺少content字段的响应
host.CallOnHttpCall([][2]string{
{":status", "200"},
{"content-type", "application/json"},
}, []byte(`{
"id": "chatcmpl-123",
"choices": [
{
"index": 0,
"message": {
"role": "assistant"
}
}
]
}`))
// 验证响应体包含错误信息
response := host.GetLocalResponse()
require.NotNil(t, response)
require.Contains(t, string(response.Data), "response body does not contain the content")
// 完成HTTP请求
host.CompleteHttp()
})
// 测试使用自定义servicePath的请求
t.Run("request with custom service path", func(t *testing.T) {
host, status := test.NewTestHost(serviceUrlConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
// 设置请求头
host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/custom/chat"},
{":method", "POST"},
{"Content-Type", "application/json"},
})
// 设置请求体
body := `{
"model": "gpt-3.5-turbo",
"messages": [
{"role": "user", "content": "What is AI?"}
]
}`
action := host.CallOnHttpRequestBody([]byte(body))
// 应该返回ActionPause等待外部服务响应
require.Equal(t, types.ActionPause, action)
// 模拟外部服务返回成功响应
host.CallOnHttpCall([][2]string{
{":status", "200"},
{"content-type", "application/json"},
}, []byte(`{
"id": "chatcmpl-123",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "{\"answer\": \"AI is artificial intelligence\"}"
}
}
]
}`))
// 验证响应体是提取的JSON内容
response := host.GetLocalResponse()
require.NotNil(t, response)
require.Contains(t, string(response.Data), "answer")
// 完成HTTP请求
host.CompleteHttp()
})
// 测试达到最大重试次数的情况
t.Run("max retry count exceeded", func(t *testing.T) {
host, status := test.NewTestHost(basicConfig)
defer host.Reset()
require.Equal(t, types.OnPluginStartStatusOK, status)
// 设置请求头
host.CallOnHttpRequestHeaders([][2]string{
{":authority", "example.com"},
{":path", "/v1/chat/completions"},
{":method", "POST"},
{"Content-Type", "application/json"},
})
// 设置请求体
body := `{
"model": "gpt-3.5-turbo",
"messages": [
{"role": "user", "content": "What is AI?"}
]
}`
action := host.CallOnHttpRequestBody([]byte(body))
// 应该返回ActionPause等待外部服务响应
require.Equal(t, types.ActionPause, action)
// 模拟多次重试每次都返回无效的content
for i := 0; i < 4; i++ { // 超过最大重试次数3次
host.CallOnHttpCall([][2]string{
{":status", "200"},
{"content-type", "application/json"},
}, []byte(`{
"id": "chatcmpl-123",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "AI is artificial intelligence"
}
}
]
}`))
}
// 验证最终响应体包含重试次数超限的错误信息
response := host.GetLocalResponse()
require.NotNil(t, response)
require.Contains(t, string(response.Data), "retry count exceeds max retry count")
// 完成HTTP请求
host.CompleteHttp()
})
})
}
func TestRejectStruct(t *testing.T) {
// 测试RejectStruct的GetBytes方法
t.Run("GetBytes", func(t *testing.T) {
reject := RejectStruct{
RejectCode: 1001,
RejectMsg: "Test error message",
}
bytes := reject.GetBytes()
require.NotNil(t, bytes)
// 验证JSON格式
var result RejectStruct
err := json.Unmarshal(bytes, &result)
require.NoError(t, err)
require.Equal(t, uint32(1001), result.RejectCode)
require.Equal(t, "Test error message", result.RejectMsg)
})
// 测试RejectStruct的GetShortMsg方法
t.Run("GetShortMsg", func(t *testing.T) {
reject := RejectStruct{
RejectCode: 1001,
RejectMsg: "Json Schema is not valid: invalid format",
}
shortMsg := reject.GetShortMsg()
require.Equal(t, "ai-json-resp.Json Schema is not valid", shortMsg)
})
// 测试RejectStruct的GetShortMsg方法 - 没有冒号的情况
t.Run("GetShortMsg no colon", func(t *testing.T) {
reject := RejectStruct{
RejectCode: 1001,
RejectMsg: "Simple error message",
}
shortMsg := reject.GetShortMsg()
require.Equal(t, "ai-json-resp.Simple error message", shortMsg)
})
}
func TestValidateBody(t *testing.T) {
// 创建测试配置
config := &PluginConfig{
contentPath: "choices.0.message.content",
jsonSchema: nil, // 明确设置为nil禁用JSON Schema验证
enableJsonSchemaValidation: false, // 禁用JSON Schema验证
}
// 测试有效的响应体
t.Run("valid response body", func(t *testing.T) {
validBody := []byte(`{
"id": "chatcmpl-123",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Hello, how can I help you?"
}
}
]
}`)
err := config.ValidateBody(validBody)
require.NoError(t, err)
})
// 测试无效的JSON响应体
t.Run("invalid JSON response body", func(t *testing.T) {
invalidBody := []byte(`invalid json content`)
err := config.ValidateBody(invalidBody)
require.Error(t, err)
require.Equal(t, uint32(SERVICE_UNAVAILABLE_CODE), config.rejectStruct.RejectCode)
require.Contains(t, config.rejectStruct.RejectMsg, "service unavailable")
})
// 测试缺少content字段的响应体
t.Run("missing content field", func(t *testing.T) {
missingContentBody := []byte(`{
"id": "chatcmpl-123",
"choices": [
{
"index": 0,
"message": {
"role": "assistant"
}
}
]
}`)
err := config.ValidateBody(missingContentBody)
require.Error(t, err)
require.Equal(t, uint32(SERVICE_UNAVAILABLE_CODE), config.rejectStruct.RejectCode)
require.Contains(t, config.rejectStruct.RejectMsg, "response body does not contain the content")
})
// 测试空的响应体
t.Run("empty response body", func(t *testing.T) {
emptyBody := []byte{}
err := config.ValidateBody(emptyBody)
require.Error(t, err)
require.Equal(t, uint32(SERVICE_UNAVAILABLE_CODE), config.rejectStruct.RejectCode)
})
}
func TestExtractJson(t *testing.T) {
// 创建测试配置
config := &PluginConfig{
jsonSchema: nil, // 明确设置为nil禁用JSON Schema验证
enableJsonSchemaValidation: false, // 禁用JSON Schema验证
}
// 测试提取有效的JSON
t.Run("extract valid JSON", func(t *testing.T) {
content := `Here is the response: {"name": "John", "age": 30} and some other text`
jsonStr, err := config.ExtractJson(content)
require.NoError(t, err)
require.Equal(t, `{"name": "John", "age": 30}`, jsonStr)
})
// 测试提取嵌套JSON
t.Run("extract nested JSON", func(t *testing.T) {
content := `Response: {"user": {"name": "John", "profile": {"age": 30, "city": "NYC"}}}`
jsonStr, err := config.ExtractJson(content)
require.NoError(t, err)
require.Equal(t, `{"user": {"name": "John", "profile": {"age": 30, "city": "NYC"}}}`, jsonStr)
})
// 测试没有JSON的内容
t.Run("no JSON in content", func(t *testing.T) {
content := `This is just plain text without any JSON content`
_, err := config.ExtractJson(content)
require.Error(t, err)
require.Contains(t, err.Error(), "cannot find json in the response body")
})
// 测试只有开始括号的内容
t.Run("only opening brace", func(t *testing.T) {
content := `Here is the start: { but no closing brace`
_, err := config.ExtractJson(content)
require.Error(t, err)
require.Contains(t, err.Error(), "cannot find json in the response body")
})
// 测试只有结束括号的内容
t.Run("only closing brace", func(t *testing.T) {
content := `Here is the end: } but no opening brace`
_, err := config.ExtractJson(content)
require.Error(t, err)
require.Contains(t, err.Error(), "cannot find json in the response body")
})
// 测试无效的JSON格式
t.Run("invalid JSON format", func(t *testing.T) {
content := `Here is invalid JSON: {"name": "John", "age": 30,}`
_, err := config.ExtractJson(content)
require.Error(t, err)
// ExtractJson会提取到{"name": "John", "age": 30,}但json.Unmarshal会失败
// 因为JSON格式无效末尾有多余的逗号
require.Contains(t, err.Error(), "invalid character '}' looking for beginning of object key string")
})
// 测试多个JSON对象应该提取第一个完整的
t.Run("multiple JSON objects", func(t *testing.T) {
content := `First: {"name": "John"} Second: {"age": 30}`
_, err := config.ExtractJson(content)
require.Error(t, err)
// ExtractJson会提取到{"name": "John"} Second: {"age": 30}
// 这不是有效的JSON因为"Second: {"age": 30}"不是有效的JSON语法
require.Contains(t, err.Error(), "invalid character 'S' after top-level value")
})
}