2026-07-31 09:54:59 +03:00
package main
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestHunyuanServiceEndpoint ( t * testing . T ) {
2026-07-31 10:37:20 +03:00
svc := NewHunyuanService ( "https://example.com/gradio/" , "" )
2026-07-31 09:54:59 +03:00
if svc . endpoint != "https://example.com/gradio" {
t . Errorf ( "expected trailing slash stripped, got %q" , svc . endpoint )
}
models := svc . ListModels ()
if len ( models ) != 1 || models [ 0 ]. ID != "hy3" {
t . Fatalf ( "expected model ID 'hy3', got %v" , models )
}
}
2026-07-31 10:37:20 +03:00
func TestCustomModelNameConfiguration ( t * testing . T ) {
svc := NewHunyuanService ( "https://example.com/gradio" , "hunyuan-custom-v1" )
if svc . modelName != "hunyuan-custom-v1" {
t . Errorf ( "expected modelName 'hunyuan-custom-v1', got %q" , svc . modelName )
}
models := svc . ListModels ()
if len ( models ) != 1 || models [ 0 ]. ID != "hunyuan-custom-v1" {
t . Fatalf ( "expected model ID 'hunyuan-custom-v1', got %v" , models )
}
}
2026-07-31 09:54:59 +03:00
func TestParseGradioStreamData ( t * testing . T ) {
sampleJSON := `[["Hello world", "Thinking process...", null]]`
c , r , tc , ok := parseGradioStreamData ( sampleJSON )
if ! ok {
t . Fatalf ( "expected parseGradioStreamData to succeed" )
}
if c != "Hello world" {
t . Errorf ( "content=%q, want 'Hello world'" , c )
}
if r != "Thinking process..." {
t . Errorf ( "reasoning=%q, want 'Thinking process...'" , r )
}
if len ( tc ) != 0 {
t . Errorf ( "expected 0 tool calls, got %d" , len ( tc ))
}
}
func TestHunyuanMockedCompletion ( t * testing . T ) {
var receivedCallURL , receivedStreamURL string
handler := http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
if strings . HasSuffix ( r . URL . Path , "/gradio_api/call/chat" ) && r . Method == http . MethodPost {
receivedCallURL = r . URL . Path
w . Header (). Set ( "Content-Type" , "application/json" )
fmt . Fprint ( w , `{"event_id":"test-event-123"}` )
return
}
if strings . Contains ( r . URL . Path , "/gradio_api/call/chat/test-event-123" ) && r . Method == http . MethodGet {
receivedStreamURL = r . URL . Path
w . Header (). Set ( "Content-Type" , "text/event-stream" )
fmt . Fprint ( w , "data: [[\"Mocked answer\", \"Mocked reasoning\", null]]\n\n" )
return
}
http . NotFound ( w , r )
})
server := httptest . NewServer ( handler )
defer server . Close ()
2026-07-31 10:37:20 +03:00
svc := NewHunyuanService ( server . URL , "hy3" )
2026-07-31 09:54:59 +03:00
rec := httptest . NewRecorder ()
req , _ := http . NewRequest ( "POST" , "/v1/chat/completions" , nil )
chatReq := ChatCompletionRequest {
Model : "hy3" ,
Messages : [] ChatMessage {
{ Role : "user" , Content : "Hello!" },
},
Stream : false ,
}
err := svc . Chat ( rec , req , chatReq )
if err != nil {
t . Fatalf ( "unexpected Chat error: %v" , err )
}
if receivedCallURL != "/gradio_api/call/chat" {
t . Errorf ( "expected call URL '/gradio_api/call/chat', got %q" , receivedCallURL )
}
if receivedStreamURL != "/gradio_api/call/chat/test-event-123" {
t . Errorf ( "expected stream URL '/gradio_api/call/chat/test-event-123', got %q" , receivedStreamURL )
}
var resp ChatCompletionResponse
if err := json . Unmarshal ( rec . Body . Bytes (), & resp ); err != nil {
t . Fatalf ( "failed to decode completion response: %v" , err )
}
if len ( resp . Choices ) != 1 {
t . Fatalf ( "expected 1 choice, got %d" , len ( resp . Choices ))
}
if resp . Choices [ 0 ]. Message . GetContentString () != "Mocked answer" {
t . Errorf ( "content=%q, want 'Mocked answer'" , resp . Choices [ 0 ]. Message . GetContentString ())
}
if resp . Choices [ 0 ]. Message . ReasoningContent != "Mocked reasoning" {
t . Errorf ( "reasoning=%q, want 'Mocked reasoning'" , resp . Choices [ 0 ]. Message . ReasoningContent )
}
}
func TestParseGradioStreamDataWithToolCalls ( t * testing . T ) {
sampleJSON := `[["", "Let me check the weather.", [{"id":"call_999","type":"function","function":{"name":"get_weather","arguments":"{\"location\":\"Tokyo\"}"}}]]]`
c , r , tc , ok := parseGradioStreamData ( sampleJSON )
if ! ok {
t . Fatalf ( "expected parseGradioStreamData to succeed" )
}
if c != "" {
t . Errorf ( "content=%q, want empty" , c )
}
if r != "Let me check the weather." {
t . Errorf ( "reasoning=%q, want 'Let me check the weather.'" , r )
}
if len ( tc ) != 1 {
t . Fatalf ( "expected 1 tool call, got %d" , len ( tc ))
}
if tc [ 0 ]. ID != "call_999" || tc [ 0 ]. Function . Name != "get_weather" || tc [ 0 ]. Function . Arguments != `{"location":"Tokyo"}` {
t . Errorf ( "unexpected tool call payload: %+v" , tc [ 0 ])
}
}
func TestHunyuanToolCallingNonStreaming ( t * testing . T ) {
handler := http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
if strings . HasSuffix ( r . URL . Path , "/gradio_api/call/chat" ) && r . Method == http . MethodPost {
var body map [ string ] interface {}
json . NewDecoder ( r . Body ). Decode ( & body )
dataArr , _ := body [ "data" ].([] interface {})
if len ( dataArr ) < 9 || dataArr [ 8 ] == nil || dataArr [ 8 ] == "" {
t . Errorf ( "expected functionsJSONStr in data[8], got %v" , dataArr )
}
w . Header (). Set ( "Content-Type" , "application/json" )
fmt . Fprint ( w , `{"event_id":"evt-tool-1"}` )
return
}
if strings . Contains ( r . URL . Path , "/gradio_api/call/chat/evt-tool-1" ) && r . Method == http . MethodGet {
w . Header (). Set ( "Content-Type" , "text/event-stream" )
fmt . Fprint ( w , "data: [[\"\", \"I should call the search tool.\", [{\"id\":\"call_abc\",\"type\":\"function\",\"function\":{\"name\":\"search\",\"arguments\":\"{\\\"query\\\":\\\"golang\\\"}\"}}]]]\n\n" )
return
}
http . NotFound ( w , r )
})
server := httptest . NewServer ( handler )
defer server . Close ()
2026-07-31 10:37:20 +03:00
svc := NewHunyuanService ( server . URL , "hy3" )
2026-07-31 09:54:59 +03:00
rec := httptest . NewRecorder ()
req , _ := http . NewRequest ( "POST" , "/v1/chat/completions" , nil )
chatReq := ChatCompletionRequest {
Model : "hy3" ,
Messages : [] ChatMessage {
{ Role : "user" , Content : "Search for golang news" },
},
Tools : [] Tool {
{
Type : "function" ,
Function : map [ string ] interface {}{
"name" : "search" ,
"description" : "Search web" ,
},
},
},
Stream : false ,
}
err := svc . Chat ( rec , req , chatReq )
if err != nil {
t . Fatalf ( "unexpected Chat error: %v" , err )
}
var resp ChatCompletionResponse
if err := json . Unmarshal ( rec . Body . Bytes (), & resp ); err != nil {
t . Fatalf ( "failed to decode response: %v" , err )
}
if len ( resp . Choices ) != 1 {
t . Fatalf ( "expected 1 choice, got %d" , len ( resp . Choices ))
}
choice := resp . Choices [ 0 ]
if choice . FinishReason != "tool_calls" {
t . Errorf ( "finish_reason=%q, want 'tool_calls'" , choice . FinishReason )
}
if choice . Message . ReasoningContent != "I should call the search tool." {
t . Errorf ( "reasoning_content=%q, want 'I should call the search tool.'" , choice . Message . ReasoningContent )
}
if len ( choice . Message . ToolCalls ) != 1 {
t . Fatalf ( "expected 1 tool call, got %d" , len ( choice . Message . ToolCalls ))
}
tc := choice . Message . ToolCalls [ 0 ]
if tc . ID != "call_abc" || tc . Function . Name != "search" || tc . Function . Arguments != `{"query":"golang"}` {
t . Errorf ( "unexpected tool call: %+v" , tc )
}
}
func TestHunyuanReasoningAndToolCallingStreaming ( t * testing . T ) {
handler := http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
if strings . HasSuffix ( r . URL . Path , "/gradio_api/call/chat" ) && r . Method == http . MethodPost {
w . Header (). Set ( "Content-Type" , "application/json" )
fmt . Fprint ( w , `{"event_id":"evt-stream-1"}` )
return
}
if strings . Contains ( r . URL . Path , "/gradio_api/call/chat/evt-stream-1" ) && r . Method == http . MethodGet {
w . Header (). Set ( "Content-Type" , "text/event-stream" )
flusher , _ := w .( http . Flusher )
fmt . Fprint ( w , "data: [[\"\", \"Step 1 reasoning.\", null]]\n\n" )
if flusher != nil {
flusher . Flush ()
}
fmt . Fprint ( w , "data: [[\"Hello \", \"Step 1 reasoning. Step 2 reasoning.\", null]]\n\n" )
if flusher != nil {
flusher . Flush ()
}
fmt . Fprint ( w , "data: [[\"Hello \", \"Step 1 reasoning. Step 2 reasoning.\", [{\"id\":\"call_xyz\",\"type\":\"function\",\"function\":{\"name\":\"calculator\",\"arguments\":\"{\\\"expr\\\":\\\"2+2\\\"}\"}}]]]\n\n" )
if flusher != nil {
flusher . Flush ()
}
return
}
http . NotFound ( w , r )
})
server := httptest . NewServer ( handler )
defer server . Close ()
2026-07-31 10:37:20 +03:00
svc := NewHunyuanService ( server . URL , "hy3" )
2026-07-31 09:54:59 +03:00
rec := httptest . NewRecorder ()
req , _ := http . NewRequest ( "POST" , "/v1/chat/completions" , nil )
chatReq := ChatCompletionRequest {
Model : "hy3" ,
Messages : [] ChatMessage {
{ Role : "user" , Content : "Calculate 2+2" },
},
Stream : true ,
}
err := svc . Chat ( rec , req , chatReq )
if err != nil {
t . Fatalf ( "unexpected Chat error: %v" , err )
}
bodyStr := rec . Body . String ()
lines := strings . Split ( bodyStr , "\n" )
var receivedDeltas [] StreamDelta
var finishReasons [] string
var foundDone bool
for _ , line := range lines {
line = strings . TrimSpace ( line )
if strings . HasPrefix ( line , "data: " ) {
payload := strings . TrimPrefix ( line , "data: " )
if payload == "[DONE]" {
foundDone = true
continue
}
var streamResp StreamResponse
if err := json . Unmarshal ([] byte ( payload ), & streamResp ); err == nil && len ( streamResp . Choices ) > 0 {
choice := streamResp . Choices [ 0 ]
receivedDeltas = append ( receivedDeltas , choice . Delta )
if choice . FinishReason != nil {
finishReasons = append ( finishReasons , * choice . FinishReason )
}
}
}
}
if ! foundDone {
t . Errorf ( "expected 'data: [DONE]' in SSE stream" )
}
if len ( receivedDeltas ) < 1 || receivedDeltas [ 0 ]. Role != "assistant" {
t . Errorf ( "expected first delta to set role='assistant', got %+v" , receivedDeltas [ 0 ])
}
var combinedReasoning , combinedContent string
var toolCallsEmitted [] ToolCall
for _ , d := range receivedDeltas {
combinedReasoning += d . ReasoningContent
combinedContent += d . Content
if len ( d . ToolCalls ) > 0 {
toolCallsEmitted = append ( toolCallsEmitted , d . ToolCalls ... )
}
}
if combinedReasoning != "Step 1 reasoning. Step 2 reasoning." {
t . Errorf ( "combined reasoning=%q, want 'Step 1 reasoning. Step 2 reasoning.'" , combinedReasoning )
}
if combinedContent != "Hello " {
t . Errorf ( "combined content=%q, want 'Hello '" , combinedContent )
}
if len ( toolCallsEmitted ) == 0 {
t . Fatalf ( "expected tool call deltas to be emitted" )
}
if len ( finishReasons ) != 1 || finishReasons [ 0 ] != "tool_calls" {
t . Errorf ( "finish_reasons=%v, want ['tool_calls']" , finishReasons )
}
}