Add comprehensive test coverage for all request builder helper functions in helpers_test.go. The new test file verifies: 1. CreateProxyRequest() - correct method, URL, headers, and body handling 2. CreateMessagesRequest() - POST /v1/messages requests 3. CreateStreamingRequestBody() - valid streaming JSON with stream:true 4. CreateNonStreamingRequestBody() - valid non-streaming JSON 5. CreateStreamingMessagesRequest() - streaming requests 6. CreateNonStreamingMessagesRequest() - non-streaming requests 7. CreateTestRequestBody() - custom request bodies with model/messages/stream 8. MustCreateTestRequestBody() - panic behavior on error All 33 sub-tests pass, confirming that the request builder functions create valid HTTP requests with correct structure, headers, and bodies for both streaming and non-streaming scenarios. Co-Authored-By: Claude <noreply@anthropic.com>
538 lines
17 KiB
Go
538 lines
17 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestRequestBuilders verifies that all request builder functions work correctly
|
|
func TestRequestBuilders(t *testing.T) {
|
|
|
|
// ============================================================================
|
|
// CreateProxyRequest() tests
|
|
// ============================================================================
|
|
t.Run("CreateProxyRequest", func(t *testing.T) {
|
|
t.Run("sets correct method", func(t *testing.T) {
|
|
req := CreateProxyRequest("POST", "/v1/messages", "")
|
|
if req.Method != "POST" {
|
|
t.Errorf("Expected method POST, got %s", req.Method)
|
|
}
|
|
})
|
|
|
|
t.Run("sets correct method for GET", func(t *testing.T) {
|
|
req := CreateProxyRequest("GET", "/v1/models", "")
|
|
if req.Method != "GET" {
|
|
t.Errorf("Expected method GET, got %s", req.Method)
|
|
}
|
|
})
|
|
|
|
t.Run("sets correct URL path", func(t *testing.T) {
|
|
req := CreateProxyRequest("POST", "/v1/messages", "")
|
|
if req.URL.Path != "/v1/messages" {
|
|
t.Errorf("Expected path /v1/messages, got %s", req.URL.Path)
|
|
}
|
|
})
|
|
|
|
t.Run("sets Authorization header", func(t *testing.T) {
|
|
req := CreateProxyRequest("POST", "/v1/messages", "")
|
|
authHeader := req.Header.Get("Authorization")
|
|
if authHeader != "Bearer test-key" {
|
|
t.Errorf("Expected Authorization header 'Bearer test-key', got %q", authHeader)
|
|
}
|
|
})
|
|
|
|
t.Run("sets Content-Type header", func(t *testing.T) {
|
|
req := CreateProxyRequest("POST", "/v1/messages", "")
|
|
contentType := req.Header.Get("Content-Type")
|
|
if contentType != "application/json" {
|
|
t.Errorf("Expected Content-Type 'application/json', got %q", contentType)
|
|
}
|
|
})
|
|
|
|
t.Run("handles body correctly - with body", func(t *testing.T) {
|
|
body := `{"test":"data"}`
|
|
req := CreateProxyRequest("POST", "/v1/messages", body)
|
|
bodyBytes, _ := io.ReadAll(req.Body)
|
|
if string(bodyBytes) != body {
|
|
t.Errorf("Expected body %q, got %q", body, string(bodyBytes))
|
|
}
|
|
})
|
|
|
|
t.Run("handles body correctly - empty body", func(t *testing.T) {
|
|
req := CreateProxyRequest("POST", "/v1/messages", "")
|
|
if req.Body != nil {
|
|
bodyBytes, _ := io.ReadAll(req.Body)
|
|
if len(bodyBytes) != 0 {
|
|
t.Errorf("Expected empty body, got %q", string(bodyBytes))
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("handles body correctly - nil when empty", func(t *testing.T) {
|
|
req := CreateProxyRequest("POST", "/v1/messages", "")
|
|
// httptest.NewRequest creates a non-nil Body even for empty strings
|
|
// but the body should be readable and empty
|
|
if req.Body == nil {
|
|
t.Error("Body should not be nil")
|
|
}
|
|
bodyBytes, _ := io.ReadAll(req.Body)
|
|
if len(bodyBytes) != 0 {
|
|
t.Errorf("Expected readable empty body, got %d bytes", len(bodyBytes))
|
|
}
|
|
})
|
|
})
|
|
|
|
// ============================================================================
|
|
// CreateMessagesRequest() tests
|
|
// ============================================================================
|
|
t.Run("CreateMessagesRequest", func(t *testing.T) {
|
|
t.Run("creates POST /v1/messages request", func(t *testing.T) {
|
|
body := `{"test":"data"}`
|
|
req := CreateMessagesRequest(body)
|
|
|
|
if req.Method != "POST" {
|
|
t.Errorf("Expected method POST, got %s", req.Method)
|
|
}
|
|
|
|
if req.URL.Path != "/v1/messages" {
|
|
t.Errorf("Expected path /v1/messages, got %s", req.URL.Path)
|
|
}
|
|
})
|
|
|
|
t.Run("includes body in request", func(t *testing.T) {
|
|
body := `{"model":"glm-4","messages":[{"role":"user","content":"Hello"}]}`
|
|
req := CreateMessagesRequest(body)
|
|
|
|
bodyBytes, _ := io.ReadAll(req.Body)
|
|
if string(bodyBytes) != body {
|
|
t.Errorf("Expected body %q, got %q", body, string(bodyBytes))
|
|
}
|
|
})
|
|
|
|
t.Run("sets Authorization header", func(t *testing.T) {
|
|
req := CreateMessagesRequest(`{}`)
|
|
authHeader := req.Header.Get("Authorization")
|
|
if authHeader != "Bearer test-key" {
|
|
t.Errorf("Expected Authorization header 'Bearer test-key', got %q", authHeader)
|
|
}
|
|
})
|
|
|
|
t.Run("sets Content-Type header", func(t *testing.T) {
|
|
req := CreateMessagesRequest(`{}`)
|
|
contentType := req.Header.Get("Content-Type")
|
|
if contentType != "application/json" {
|
|
t.Errorf("Expected Content-Type 'application/json', got %q", contentType)
|
|
}
|
|
})
|
|
})
|
|
|
|
// ============================================================================
|
|
// CreateStreamingRequestBody() tests
|
|
// ============================================================================
|
|
t.Run("CreateStreamingRequestBody", func(t *testing.T) {
|
|
t.Run("generates valid streaming request JSON", func(t *testing.T) {
|
|
body := CreateStreamingRequestBody()
|
|
|
|
// Verify it's valid JSON
|
|
var parsed map[string]interface{}
|
|
if err := json.Unmarshal([]byte(body), &parsed); err != nil {
|
|
t.Fatalf("Generated body is not valid JSON: %v", err)
|
|
}
|
|
|
|
// Verify it has the required fields
|
|
if parsed["model"] != "glm-4" {
|
|
t.Errorf("Expected model 'glm-4', got %v", parsed["model"])
|
|
}
|
|
|
|
// Verify stream field is true
|
|
streamValue, exists := parsed["stream"]
|
|
if !exists {
|
|
t.Error("Streaming body should have 'stream' field")
|
|
} else if streamValue != true {
|
|
t.Errorf("Expected stream=true, got %v", streamValue)
|
|
}
|
|
|
|
// Verify messages array exists and has content
|
|
messages, exists := parsed["messages"]
|
|
if !exists {
|
|
t.Error("Streaming body should have 'messages' field")
|
|
} else {
|
|
msgs, ok := messages.([]interface{})
|
|
if !ok || len(msgs) == 0 {
|
|
t.Error("Messages should be a non-empty array")
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("contains stream:true field", func(t *testing.T) {
|
|
body := CreateStreamingRequestBody()
|
|
if !strings.Contains(body, `"stream":true`) {
|
|
t.Errorf("Expected body to contain 'stream:true', got %q", body)
|
|
}
|
|
})
|
|
|
|
t.Run("contains model field", func(t *testing.T) {
|
|
body := CreateStreamingRequestBody()
|
|
if !strings.Contains(body, `"model":"glm-4"`) {
|
|
t.Errorf("Expected body to contain model, got %q", body)
|
|
}
|
|
})
|
|
|
|
t.Run("contains messages array", func(t *testing.T) {
|
|
body := CreateStreamingRequestBody()
|
|
if !strings.Contains(body, `"messages"`) {
|
|
t.Errorf("Expected body to contain messages array, got %q", body)
|
|
}
|
|
})
|
|
})
|
|
|
|
// ============================================================================
|
|
// CreateNonStreamingRequestBody() tests
|
|
// ============================================================================
|
|
t.Run("CreateNonStreamingRequestBody", func(t *testing.T) {
|
|
t.Run("generates valid non-streaming request JSON", func(t *testing.T) {
|
|
body := CreateNonStreamingRequestBody()
|
|
|
|
// Verify it's valid JSON
|
|
var parsed map[string]interface{}
|
|
if err := json.Unmarshal([]byte(body), &parsed); err != nil {
|
|
t.Fatalf("Generated body is not valid JSON: %v", err)
|
|
}
|
|
|
|
// Verify it has the required fields
|
|
if parsed["model"] != "glm-4" {
|
|
t.Errorf("Expected model 'glm-4', got %v", parsed["model"])
|
|
}
|
|
|
|
// Verify stream field is absent or false
|
|
streamValue, exists := parsed["stream"]
|
|
if exists && streamValue != false {
|
|
t.Errorf("Expected no stream field or stream=false, got %v", streamValue)
|
|
}
|
|
|
|
// Verify messages array exists and has content
|
|
messages, exists := parsed["messages"]
|
|
if !exists {
|
|
t.Error("Non-streaming body should have 'messages' field")
|
|
} else {
|
|
msgs, ok := messages.([]interface{})
|
|
if !ok || len(msgs) == 0 {
|
|
t.Error("Messages should be a non-empty array")
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("does not contain stream:true field", func(t *testing.T) {
|
|
body := CreateNonStreamingRequestBody()
|
|
if strings.Contains(body, `"stream":true`) {
|
|
t.Errorf("Non-streaming body should not contain 'stream:true', got %q", body)
|
|
}
|
|
})
|
|
|
|
t.Run("contains model field", func(t *testing.T) {
|
|
body := CreateNonStreamingRequestBody()
|
|
if !strings.Contains(body, `"model":"glm-4"`) {
|
|
t.Errorf("Expected body to contain model, got %q", body)
|
|
}
|
|
})
|
|
|
|
t.Run("contains messages array", func(t *testing.T) {
|
|
body := CreateNonStreamingRequestBody()
|
|
if !strings.Contains(body, `"messages"`) {
|
|
t.Errorf("Expected body to contain messages array, got %q", body)
|
|
}
|
|
})
|
|
})
|
|
|
|
// ============================================================================
|
|
// CreateStreamingMessagesRequest() tests
|
|
// ============================================================================
|
|
t.Run("CreateStreamingMessagesRequest", func(t *testing.T) {
|
|
t.Run("creates streaming request", func(t *testing.T) {
|
|
req := CreateStreamingMessagesRequest()
|
|
|
|
if req.Method != "POST" {
|
|
t.Errorf("Expected method POST, got %s", req.Method)
|
|
}
|
|
|
|
if req.URL.Path != "/v1/messages" {
|
|
t.Errorf("Expected path /v1/messages, got %s", req.URL.Path)
|
|
}
|
|
})
|
|
|
|
t.Run("request body contains stream:true", func(t *testing.T) {
|
|
req := CreateStreamingMessagesRequest()
|
|
bodyBytes, _ := io.ReadAll(req.Body)
|
|
|
|
if !strings.Contains(string(bodyBytes), `"stream":true`) {
|
|
t.Errorf("Streaming request should contain 'stream:true', got %q", string(bodyBytes))
|
|
}
|
|
})
|
|
|
|
t.Run("sets correct headers", func(t *testing.T) {
|
|
req := CreateStreamingMessagesRequest()
|
|
|
|
authHeader := req.Header.Get("Authorization")
|
|
if authHeader != "Bearer test-key" {
|
|
t.Errorf("Expected Authorization 'Bearer test-key', got %q", authHeader)
|
|
}
|
|
|
|
contentType := req.Header.Get("Content-Type")
|
|
if contentType != "application/json" {
|
|
t.Errorf("Expected Content-Type 'application/json', got %q", contentType)
|
|
}
|
|
})
|
|
})
|
|
|
|
// ============================================================================
|
|
// CreateNonStreamingMessagesRequest() tests
|
|
// ============================================================================
|
|
t.Run("CreateNonStreamingMessagesRequest", func(t *testing.T) {
|
|
t.Run("creates non-streaming request", func(t *testing.T) {
|
|
req := CreateNonStreamingMessagesRequest()
|
|
|
|
if req.Method != "POST" {
|
|
t.Errorf("Expected method POST, got %s", req.Method)
|
|
}
|
|
|
|
if req.URL.Path != "/v1/messages" {
|
|
t.Errorf("Expected path /v1/messages, got %s", req.URL.Path)
|
|
}
|
|
})
|
|
|
|
t.Run("request body does not contain stream:true", func(t *testing.T) {
|
|
req := CreateNonStreamingMessagesRequest()
|
|
bodyBytes, _ := io.ReadAll(req.Body)
|
|
|
|
if strings.Contains(string(bodyBytes), `"stream":true`) {
|
|
t.Errorf("Non-streaming request should not contain 'stream:true', got %q", string(bodyBytes))
|
|
}
|
|
})
|
|
|
|
t.Run("sets correct headers", func(t *testing.T) {
|
|
req := CreateNonStreamingMessagesRequest()
|
|
|
|
authHeader := req.Header.Get("Authorization")
|
|
if authHeader != "Bearer test-key" {
|
|
t.Errorf("Expected Authorization 'Bearer test-key', got %q", authHeader)
|
|
}
|
|
|
|
contentType := req.Header.Get("Content-Type")
|
|
if contentType != "application/json" {
|
|
t.Errorf("Expected Content-Type 'application/json', got %q", contentType)
|
|
}
|
|
})
|
|
})
|
|
|
|
// ============================================================================
|
|
// CreateTestRequestBody() tests
|
|
// ============================================================================
|
|
t.Run("CreateTestRequestBody", func(t *testing.T) {
|
|
t.Run("creates custom request body with model", func(t *testing.T) {
|
|
messages := []map[string]string{
|
|
{"role": "user", "content": "Hello"},
|
|
}
|
|
body, err := CreateTestRequestBody("custom-model", messages, false)
|
|
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
var parsed map[string]interface{}
|
|
if err := json.Unmarshal([]byte(body), &parsed); err != nil {
|
|
t.Fatalf("Generated body is not valid JSON: %v", err)
|
|
}
|
|
|
|
if parsed["model"] != "custom-model" {
|
|
t.Errorf("Expected model 'custom-model', got %v", parsed["model"])
|
|
}
|
|
})
|
|
|
|
t.Run("creates custom request body with messages", func(t *testing.T) {
|
|
messages := []map[string]string{
|
|
{"role": "user", "content": "Hello"},
|
|
{"role": "assistant", "content": "Hi there"},
|
|
}
|
|
body, err := CreateTestRequestBody("glm-4", messages, false)
|
|
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
var parsed map[string]interface{}
|
|
if err := json.Unmarshal([]byte(body), &parsed); err != nil {
|
|
t.Fatalf("Generated body is not valid JSON: %v", err)
|
|
}
|
|
|
|
msgs, ok := parsed["messages"].([]interface{})
|
|
if !ok {
|
|
t.Fatal("Messages should be an array")
|
|
}
|
|
|
|
if len(msgs) != 2 {
|
|
t.Errorf("Expected 2 messages, got %d", len(msgs))
|
|
}
|
|
})
|
|
|
|
t.Run("creates streaming body when stream=true", func(t *testing.T) {
|
|
messages := []map[string]string{
|
|
{"role": "user", "content": "Hello"},
|
|
}
|
|
body, err := CreateTestRequestBody("glm-4", messages, true)
|
|
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
var parsed map[string]interface{}
|
|
if err := json.Unmarshal([]byte(body), &parsed); err != nil {
|
|
t.Fatalf("Generated body is not valid JSON: %v", err)
|
|
}
|
|
|
|
streamValue, exists := parsed["stream"]
|
|
if !exists {
|
|
t.Error("Streaming body should have 'stream' field")
|
|
} else if streamValue != true {
|
|
t.Errorf("Expected stream=true, got %v", streamValue)
|
|
}
|
|
})
|
|
|
|
t.Run("creates non-streaming body when stream=false", func(t *testing.T) {
|
|
messages := []map[string]string{
|
|
{"role": "user", "content": "Hello"},
|
|
}
|
|
body, err := CreateTestRequestBody("glm-4", messages, false)
|
|
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
var parsed map[string]interface{}
|
|
if err := json.Unmarshal([]byte(body), &parsed); err != nil {
|
|
t.Fatalf("Generated body is not valid JSON: %v", err)
|
|
}
|
|
|
|
// stream field should not exist or be false
|
|
streamValue, exists := parsed["stream"]
|
|
if exists && streamValue != false {
|
|
t.Errorf("Expected no stream field for non-streaming, got %v", streamValue)
|
|
}
|
|
})
|
|
|
|
t.Run("handles empty messages array", func(t *testing.T) {
|
|
messages := []map[string]string{}
|
|
body, err := CreateTestRequestBody("glm-4", messages, false)
|
|
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
|
|
var parsed map[string]interface{}
|
|
if err := json.Unmarshal([]byte(body), &parsed); err != nil {
|
|
t.Fatalf("Generated body is not valid JSON: %v", err)
|
|
}
|
|
|
|
msgs, ok := parsed["messages"].([]interface{})
|
|
if !ok {
|
|
t.Fatal("Messages should be an array")
|
|
}
|
|
|
|
if len(msgs) != 0 {
|
|
t.Errorf("Expected 0 messages, got %d", len(msgs))
|
|
}
|
|
})
|
|
|
|
t.Run("returns error on invalid JSON structure", func(t *testing.T) {
|
|
// This test verifies that JSON marshaling can fail
|
|
// Since our function only uses json.Marshal, it should handle any error
|
|
messages := []map[string]string{
|
|
{"role": "user", "content": "Hello"},
|
|
}
|
|
|
|
// Create a body that can be marshaled
|
|
body, err := CreateTestRequestBody("glm-4", messages, false)
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error with valid input: %v", err)
|
|
}
|
|
|
|
// Verify the body is valid JSON
|
|
var parsed map[string]interface{}
|
|
if err := json.Unmarshal([]byte(body), &parsed); err != nil {
|
|
t.Errorf("Generated body should be valid JSON: %v", err)
|
|
}
|
|
})
|
|
})
|
|
|
|
// ============================================================================
|
|
// MustCreateTestRequestBody() tests
|
|
// ============================================================================
|
|
t.Run("MustCreateTestRequestBody", func(t *testing.T) {
|
|
t.Run("returns body on success", func(t *testing.T) {
|
|
messages := []map[string]string{
|
|
{"role": "user", "content": "Hello"},
|
|
}
|
|
|
|
// This should not panic
|
|
body := MustCreateTestRequestBody("glm-4", messages, true)
|
|
|
|
// Verify the body is valid
|
|
var parsed map[string]interface{}
|
|
if err := json.Unmarshal([]byte(body), &parsed); err != nil {
|
|
t.Fatalf("Generated body is not valid JSON: %v", err)
|
|
}
|
|
|
|
if parsed["stream"] != true {
|
|
t.Error("Expected stream=true")
|
|
}
|
|
})
|
|
|
|
t.Run("panics on error - cannot test directly", func(t *testing.T) {
|
|
// Note: Testing that a function panics is difficult because
|
|
// json.Marshal rarely fails with normal inputs. The function
|
|
// would only panic if json.Marshal fails, which happens with:
|
|
// - unmarshalable types (channels, functions)
|
|
// - circular references
|
|
|
|
// Since CreateTestRequestBody only uses basic types
|
|
// (strings, maps, bools), json.Marshal will always succeed.
|
|
// The panic protection is there for API completeness.
|
|
|
|
messages := []map[string]string{
|
|
{"role": "user", "content": "Hello"},
|
|
}
|
|
|
|
// This should not panic
|
|
body := MustCreateTestRequestBody("glm-4", messages, false)
|
|
if body == "" {
|
|
t.Error("Body should not be empty")
|
|
}
|
|
|
|
// Verify it's valid JSON
|
|
var parsed map[string]interface{}
|
|
if err := json.Unmarshal([]byte(body), &parsed); err != nil {
|
|
t.Errorf("Generated body should be valid JSON: %v", err)
|
|
}
|
|
})
|
|
|
|
t.Run("behavior matches CreateTestRequestBody for valid input", func(t *testing.T) {
|
|
messages := []map[string]string{
|
|
{"role": "user", "content": "Test"},
|
|
}
|
|
|
|
// Get results from both functions
|
|
body1, err := CreateTestRequestBody("glm-4", messages, true)
|
|
if err != nil {
|
|
t.Fatalf("CreateTestRequestBody failed: %v", err)
|
|
}
|
|
|
|
body2 := MustCreateTestRequestBody("glm-4", messages, true)
|
|
|
|
// They should be identical
|
|
if body1 != body2 {
|
|
t.Errorf("Results differ:\nCreateTestRequestBody: %q\nMustCreateTestRequestBody: %q", body1, body2)
|
|
}
|
|
})
|
|
})
|
|
}
|