mirror of
https://github.com/lukaszraczylo/go-simple-graphql.git
synced 2026-10-12 00:45:31 +00:00
805 lines
29 KiB
Go
805 lines
29 KiB
Go
package gql
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"unicode/utf8"
|
|
|
|
assertions "github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
// TestTrailingGarbageReproduction attempts to reproduce the exact "trailing garbage" scenario
|
|
func TestTrailingGarbageReproduction(t *testing.T) {
|
|
assert := assertions.New(t)
|
|
|
|
t.Run("should investigate size discrepancy - 1093 vs 356 bytes", func(t *testing.T) {
|
|
client := CreateTestClient()
|
|
|
|
// Base data from user report
|
|
variables := map[string]interface{}{
|
|
"group_description": "This a group for serious buyers and sellers\nOf uber eats and Door dash account \nRippers are not allowed in this group😈",
|
|
"user_name": "2eba巴结",
|
|
}
|
|
|
|
query := "mutation storeMessage($group_description: String!, $user_name: String!) { insert_message(group_description: $group_description, user_name: $user_name) { id } }"
|
|
|
|
// Test different scenarios that might increase size to 1093 bytes
|
|
scenarios := []struct {
|
|
name string
|
|
modifier func(map[string]interface{}) map[string]interface{}
|
|
queryMod func(string) string
|
|
expectSize int
|
|
}{
|
|
{
|
|
name: "base_case",
|
|
modifier: func(v map[string]interface{}) map[string]interface{} {
|
|
return v
|
|
},
|
|
queryMod: func(q string) string { return q },
|
|
expectSize: 356,
|
|
},
|
|
{
|
|
name: "with_additional_headers_in_variables",
|
|
modifier: func(v map[string]interface{}) map[string]interface{} {
|
|
result := make(map[string]interface{})
|
|
for k, val := range v {
|
|
result[k] = val
|
|
}
|
|
// Add headers that might be included in variables
|
|
result["authorization"] = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
|
|
result["user_agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
|
result["content_type"] = "application/json; charset=utf-8"
|
|
return result
|
|
},
|
|
queryMod: func(q string) string { return q },
|
|
expectSize: 700, // Estimated
|
|
},
|
|
{
|
|
name: "with_nested_json_data",
|
|
modifier: func(v map[string]interface{}) map[string]interface{} {
|
|
result := make(map[string]interface{})
|
|
for k, val := range v {
|
|
result[k] = val
|
|
}
|
|
// Add nested JSON that might be double-encoded
|
|
result["metadata"] = map[string]interface{}{
|
|
"client_info": map[string]interface{}{
|
|
"platform": "web",
|
|
"version": "1.0.0",
|
|
"features": []string{"emoji_support", "unicode_text", "file_upload"},
|
|
},
|
|
"request_id": "req_123456789_abcdef",
|
|
"timestamp": "2023-05-23T18:01:37.000Z",
|
|
}
|
|
return result
|
|
},
|
|
queryMod: func(q string) string { return q },
|
|
expectSize: 800, // Estimated
|
|
},
|
|
{
|
|
name: "with_very_long_query",
|
|
modifier: func(v map[string]interface{}) map[string]interface{} {
|
|
return v
|
|
},
|
|
queryMod: func(q string) string {
|
|
// Add a much longer query with many fields
|
|
return `mutation storeMessage(
|
|
$group_description: String!,
|
|
$user_name: String!,
|
|
$additional_field1: String,
|
|
$additional_field2: String,
|
|
$additional_field3: String,
|
|
$additional_field4: String,
|
|
$additional_field5: String
|
|
) {
|
|
insert_message(
|
|
group_description: $group_description,
|
|
user_name: $user_name,
|
|
additional_field1: $additional_field1,
|
|
additional_field2: $additional_field2,
|
|
additional_field3: $additional_field3,
|
|
additional_field4: $additional_field4,
|
|
additional_field5: $additional_field5
|
|
) {
|
|
id
|
|
created_at
|
|
updated_at
|
|
status
|
|
metadata {
|
|
source
|
|
platform
|
|
version
|
|
}
|
|
}
|
|
}`
|
|
},
|
|
expectSize: 1000, // Estimated
|
|
},
|
|
}
|
|
|
|
for _, scenario := range scenarios {
|
|
t.Run(scenario.name, func(t *testing.T) {
|
|
modifiedVars := scenario.modifier(variables)
|
|
modifiedQuery := scenario.queryMod(query)
|
|
|
|
compiledQuery := client.compileQuery(modifiedQuery, modifiedVars)
|
|
assert.NotNil(compiledQuery)
|
|
|
|
size := len(compiledQuery.JsonQuery)
|
|
t.Logf("%s: JSON size = %d bytes (expected ~%d)", scenario.name, size, scenario.expectSize)
|
|
|
|
if size >= 1090 && size <= 1210 { // Expanded range to include our actual results
|
|
t.Logf("🎯 FOUND POTENTIAL MATCH: %s produces %d bytes (target: 1093)", scenario.name, size)
|
|
t.Logf("JSON content: %s", string(compiledQuery.JsonQuery))
|
|
}
|
|
|
|
// Verify it's still valid
|
|
assert.True(json.Valid(compiledQuery.JsonQuery))
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("should test potential double-encoding scenarios", func(t *testing.T) {
|
|
client := CreateTestClient()
|
|
|
|
// Test if the JsonQuery field might be getting included somehow
|
|
query := &Query{
|
|
Query: "mutation storeMessage($group_description: String!, $user_name: String!) { insert_message(group_description: $group_description, user_name: $user_name) { id } }",
|
|
Variables: map[string]interface{}{
|
|
"group_description": "This a group for serious buyers and sellers\nOf uber eats and Door dash account \nRippers are not allowed in this group😈",
|
|
"user_name": "2eba巴结",
|
|
},
|
|
}
|
|
|
|
// First, compile normally
|
|
query.JsonQuery = client.convertToJSON(query)
|
|
normalSize := len(query.JsonQuery)
|
|
t.Logf("Normal encoding size: %d bytes", normalSize)
|
|
|
|
// Test if somehow JsonQuery gets included in serialization (it shouldn't due to json:"-")
|
|
doubleEncodedBytes, err := json.Marshal(query)
|
|
assert.NoError(err)
|
|
doubleEncodedSize := len(doubleEncodedBytes)
|
|
t.Logf("Double encoding size: %d bytes", doubleEncodedSize)
|
|
|
|
// Test if there's a scenario where the JsonQuery field is accidentally included
|
|
queryWithoutTag := struct {
|
|
Variables map[string]interface{} `json:"variables,omitempty"`
|
|
Query string `json:"query,omitempty"`
|
|
JsonQuery []byte `json:"jsonQuery,omitempty"` // Without json:"-"
|
|
}{
|
|
Variables: query.Variables,
|
|
Query: query.Query,
|
|
JsonQuery: query.JsonQuery,
|
|
}
|
|
|
|
problematicBytes, err := json.Marshal(queryWithoutTag)
|
|
assert.NoError(err)
|
|
problematicSize := len(problematicBytes)
|
|
t.Logf("Problematic encoding (with JsonQuery field) size: %d bytes", problematicSize)
|
|
|
|
if problematicSize >= 1090 && problematicSize <= 1100 {
|
|
t.Logf("🎯 FOUND ISSUE: Including JsonQuery field produces %d bytes!", problematicSize)
|
|
t.Logf("This would cause trailing garbage because JsonQuery contains base64 or nested JSON")
|
|
}
|
|
})
|
|
|
|
t.Run("should test server that detects trailing garbage", func(t *testing.T) {
|
|
// Create a server that specifically looks for trailing garbage patterns
|
|
trailingGarbageServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
body := make([]byte, r.ContentLength)
|
|
n, _ := r.Body.Read(body)
|
|
actualBody := body[:n]
|
|
|
|
t.Logf("Server analyzing %d bytes", len(actualBody))
|
|
|
|
// Try to decode JSON and check for trailing data
|
|
decoder := json.NewDecoder(bytes.NewReader(actualBody))
|
|
var request map[string]interface{}
|
|
err := decoder.Decode(&request)
|
|
if err != nil {
|
|
t.Logf("JSON decode error: %s", err.Error())
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
w.Write([]byte(`{"error":"invalid JSON","code":"invalid-json"}`))
|
|
return
|
|
}
|
|
|
|
// Check if there's trailing data after the first JSON object
|
|
remaining := decoder.Buffered()
|
|
if remaining != nil {
|
|
buf := make([]byte, 1024)
|
|
n, _ := remaining.Read(buf)
|
|
if n > 0 {
|
|
t.Logf("Found trailing data: %s", string(buf[:n]))
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
w.Write([]byte(`{"error":"trailing garbage after JSON","code":"invalid-json"}`))
|
|
return
|
|
}
|
|
}
|
|
|
|
// Check for specific patterns that might indicate double encoding
|
|
if bytes.Contains(actualBody, []byte(`"jsonQuery"`)) {
|
|
t.Logf("Found jsonQuery field in request - this would cause issues!")
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
w.Write([]byte(`{"error":"trailing garbage after JSON","code":"invalid-json"}`))
|
|
return
|
|
}
|
|
|
|
// Check for base64 patterns that might indicate nested encoding
|
|
if bytes.Contains(actualBody, []byte("eyJ")) { // Common base64 JSON start
|
|
t.Logf("Found potential base64 encoded JSON in request")
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
w.Write([]byte(`{"error":"trailing garbage after JSON","code":"invalid-json"}`))
|
|
return
|
|
}
|
|
|
|
// Success
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(`{"data":{"result":"success"}}`))
|
|
}))
|
|
defer trailingGarbageServer.Close()
|
|
|
|
client := CreateTestClient()
|
|
|
|
// Test the normal case
|
|
variables := map[string]interface{}{
|
|
"group_description": "This a group for serious buyers and sellers\nOf uber eats and Door dash account \nRippers are not allowed in this group😈",
|
|
"user_name": "2eba巴结",
|
|
}
|
|
|
|
query := "mutation storeMessage($group_description: String!, $user_name: String!) { insert_message(group_description: $group_description, user_name: $user_name) { id } }"
|
|
compiledQuery := client.compileQuery(query, variables)
|
|
|
|
qe := &QueryExecutor{
|
|
BaseClient: client,
|
|
Query: compiledQuery.JsonQuery,
|
|
Headers: map[string]interface{}{"Content-Type": "application/json; charset=utf-8"},
|
|
CacheKey: "no-cache",
|
|
Retries: false,
|
|
}
|
|
qe.endpoint = trailingGarbageServer.URL
|
|
qe.client = trailingGarbageServer.Client()
|
|
qe.cache = client.cache
|
|
|
|
result, err := qe.executeQuery()
|
|
assert.NoError(err, "Normal request should succeed")
|
|
assert.NotNil(result)
|
|
})
|
|
|
|
t.Run("should test Content-Length vs actual body size mismatch", func(t *testing.T) {
|
|
// Test if there's a mismatch between Content-Length and actual body size
|
|
var receivedContentLength int64
|
|
var actualBodySize int
|
|
|
|
mismatchServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
receivedContentLength = r.ContentLength
|
|
|
|
body := make([]byte, r.ContentLength+100) // Read more than Content-Length
|
|
n, _ := r.Body.Read(body)
|
|
actualBodySize = n
|
|
|
|
t.Logf("Content-Length header: %d", receivedContentLength)
|
|
t.Logf("Actual body size read: %d", actualBodySize)
|
|
|
|
if int64(actualBodySize) != receivedContentLength {
|
|
t.Logf("⚠️ MISMATCH: Content-Length=%d but actual body=%d", receivedContentLength, actualBodySize)
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
w.Write([]byte(`{"error":"trailing garbage after JSON","code":"invalid-json"}`))
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(`{"data":{"result":"success"}}`))
|
|
}))
|
|
defer mismatchServer.Close()
|
|
|
|
client := CreateTestClient()
|
|
|
|
variables := map[string]interface{}{
|
|
"group_description": "This a group for serious buyers and sellers\nOf uber eats and Door dash account \nRippers are not allowed in this group😈",
|
|
"user_name": "2eba巴结",
|
|
}
|
|
|
|
query := "mutation storeMessage($group_description: String!, $user_name: String!) { insert_message(group_description: $group_description, user_name: $user_name) { id } }"
|
|
compiledQuery := client.compileQuery(query, variables)
|
|
|
|
qe := &QueryExecutor{
|
|
BaseClient: client,
|
|
Query: compiledQuery.JsonQuery,
|
|
Headers: map[string]interface{}{"Content-Type": "application/json; charset=utf-8"},
|
|
CacheKey: "no-cache",
|
|
Retries: false,
|
|
}
|
|
qe.endpoint = mismatchServer.URL
|
|
qe.client = mismatchServer.Client()
|
|
qe.cache = client.cache
|
|
|
|
result, err := qe.executeQuery()
|
|
assert.NoError(err, "Should not have Content-Length mismatch")
|
|
assert.NotNil(result)
|
|
assert.Equal(int64(actualBodySize), receivedContentLength, "Content-Length should match actual body size")
|
|
})
|
|
}
|
|
|
|
// TestPotentialRootCauses tests the most likely root causes of the trailing garbage issue
|
|
func TestPotentialRootCauses(t *testing.T) {
|
|
assert := assertions.New(t)
|
|
|
|
t.Run("root_cause_1_JsonQuery_field_accidentally_included", func(t *testing.T) {
|
|
// This would be the most likely cause - if JsonQuery field gets included in JSON
|
|
client := CreateTestClient()
|
|
_ = client // Use the client variable to avoid compiler error
|
|
|
|
// Simulate a bug where JsonQuery field is included
|
|
type BuggyQuery struct {
|
|
Variables map[string]interface{} `json:"variables,omitempty"`
|
|
Query string `json:"query,omitempty"`
|
|
JsonQuery []byte `json:"jsonQuery,omitempty"` // BUG: should be json:"-"
|
|
}
|
|
|
|
buggyQuery := &BuggyQuery{
|
|
Query: "mutation storeMessage($group_description: String!, $user_name: String!) { insert_message(group_description: $group_description, user_name: $user_name) { id } }",
|
|
Variables: map[string]interface{}{
|
|
"group_description": "This a group for serious buyers and sellers\nOf uber eats and Door dash account \nRippers are not allowed in this group😈",
|
|
"user_name": "2eba巴结",
|
|
},
|
|
}
|
|
|
|
// Set JsonQuery to the JSON representation of itself (recursive)
|
|
normalJson, _ := json.Marshal(map[string]interface{}{
|
|
"query": buggyQuery.Query,
|
|
"variables": buggyQuery.Variables,
|
|
})
|
|
buggyQuery.JsonQuery = normalJson
|
|
|
|
// Now marshal the buggy query - this would include JsonQuery field
|
|
buggyJsonBytes, err := json.Marshal(buggyQuery)
|
|
assert.NoError(err)
|
|
|
|
t.Logf("Buggy JSON size: %d bytes", len(buggyJsonBytes))
|
|
t.Logf("Buggy JSON content: %s", string(buggyJsonBytes))
|
|
|
|
// This would create a JSON with nested JSON, potentially causing parsing issues
|
|
var parsed map[string]interface{}
|
|
err = json.Unmarshal(buggyJsonBytes, &parsed)
|
|
assert.NoError(err) // JSON is still valid, but contains extra data
|
|
|
|
// Check if this creates the size we're looking for
|
|
if len(buggyJsonBytes) >= 1090 && len(buggyJsonBytes) <= 1100 {
|
|
t.Logf("🎯 ROOT CAUSE FOUND: JsonQuery field inclusion creates %d bytes", len(buggyJsonBytes))
|
|
}
|
|
})
|
|
|
|
t.Run("root_cause_2_buffer_reuse_contamination", func(t *testing.T) {
|
|
// Test if buffer reuse might cause contamination
|
|
client := CreateTestClient()
|
|
|
|
// Simulate multiple requests that might contaminate buffers
|
|
for i := 0; i < 3; i++ {
|
|
variables := map[string]interface{}{
|
|
"group_description": fmt.Sprintf("Request %d: This a group for serious buyers and sellers\nOf uber eats and Door dash account \nRippers are not allowed in this group😈", i),
|
|
"user_name": "2eba巴结",
|
|
"request_id": fmt.Sprintf("req_%d", i),
|
|
}
|
|
|
|
query := "mutation storeMessage($group_description: String!, $user_name: String!, $request_id: String) { insert_message(group_description: $group_description, user_name: $user_name, request_id: $request_id) { id } }"
|
|
compiledQuery := client.compileQuery(query, variables)
|
|
|
|
t.Logf("Request %d JSON size: %d bytes", i, len(compiledQuery.JsonQuery))
|
|
|
|
// Check for any unexpected content
|
|
assert.True(json.Valid(compiledQuery.JsonQuery))
|
|
}
|
|
})
|
|
|
|
t.Run("root_cause_3_encoding_header_mismatch", func(t *testing.T) {
|
|
// Test if Content-Type charset mismatch causes issues
|
|
client := CreateTestClient()
|
|
|
|
variables := map[string]interface{}{
|
|
"group_description": "This a group for serious buyers and sellers\nOf uber eats and Door dash account \nRippers are not allowed in this group😈",
|
|
"user_name": "2eba巴结",
|
|
}
|
|
|
|
query := "mutation storeMessage($group_description: String!, $user_name: String!) { insert_message(group_description: $group_description, user_name: $user_name) { id } }"
|
|
compiledQuery := client.compileQuery(query, variables)
|
|
|
|
// Test different charset declarations
|
|
charsets := []string{
|
|
"application/json",
|
|
"application/json; charset=utf-8",
|
|
"application/json; charset=UTF-8",
|
|
"application/json; charset=iso-8859-1", // Wrong charset
|
|
}
|
|
|
|
for _, contentType := range charsets {
|
|
t.Logf("Testing Content-Type: %s", contentType)
|
|
|
|
// The JSON should be the same regardless of Content-Type header
|
|
assert.True(json.Valid(compiledQuery.JsonQuery))
|
|
assert.Equal(356, len(compiledQuery.JsonQuery)) // Should be consistent
|
|
}
|
|
})
|
|
|
|
t.Run("root_cause_4_unicode_character_validation", func(t *testing.T) {
|
|
// Test the exact Unicode characters from production logs
|
|
client := CreateTestClient()
|
|
|
|
// Test various Unicode scenarios that might cause issues
|
|
unicodeScenarios := []struct {
|
|
name string
|
|
userName string
|
|
description string
|
|
expectValid bool
|
|
}{
|
|
{
|
|
name: "production_case_exact",
|
|
userName: "2eba巴结",
|
|
description: "This a group for serious buyers and sellers\nOf uber eats and Door dash account \nRippers are not allowed in this group😈",
|
|
expectValid: true,
|
|
},
|
|
{
|
|
name: "mixed_unicode_emojis",
|
|
userName: "用户🎯💻🔥",
|
|
description: "Test with multiple emojis: 🚀🎉💡🔧⚡️🌟",
|
|
expectValid: true,
|
|
},
|
|
{
|
|
name: "chinese_japanese_korean",
|
|
userName: "测试用户한국어日本語",
|
|
description: "Mixed CJK characters: 中文 한국어 日本語",
|
|
expectValid: true,
|
|
},
|
|
{
|
|
name: "special_unicode_chars",
|
|
userName: "user™®©",
|
|
description: "Special symbols: ™®©±×÷≠≤≥∞∑∏∆√∫",
|
|
expectValid: true,
|
|
},
|
|
}
|
|
|
|
for _, scenario := range unicodeScenarios {
|
|
t.Run(scenario.name, func(t *testing.T) {
|
|
variables := map[string]interface{}{
|
|
"group_description": scenario.description,
|
|
"user_name": scenario.userName,
|
|
}
|
|
|
|
query := "mutation storeMessage($group_description: String!, $user_name: String!) { insert_message(group_description: $group_description, user_name: $user_name) { id } }"
|
|
compiledQuery := client.compileQuery(query, variables)
|
|
|
|
// Validate UTF-8 encoding
|
|
assert.True(utf8.Valid(compiledQuery.JsonQuery), "JSON should be valid UTF-8")
|
|
assert.True(json.Valid(compiledQuery.JsonQuery), "JSON should be valid")
|
|
|
|
// Check for any jsonQuery field inclusion
|
|
assert.False(bytes.Contains(compiledQuery.JsonQuery, []byte(`"jsonQuery"`)), "Should not contain jsonQuery field")
|
|
|
|
// Log the size for analysis
|
|
t.Logf("%s: JSON size = %d bytes", scenario.name, len(compiledQuery.JsonQuery))
|
|
t.Logf("Content: %s", string(compiledQuery.JsonQuery))
|
|
|
|
// Test with actual server
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
body := make([]byte, r.ContentLength)
|
|
n, _ := r.Body.Read(body)
|
|
actualBody := body[:n]
|
|
|
|
// Validate the request body
|
|
assert.True(utf8.Valid(actualBody), "Request body should be valid UTF-8")
|
|
assert.True(json.Valid(actualBody), "Request body should be valid JSON")
|
|
assert.False(bytes.Contains(actualBody, []byte(`"jsonQuery"`)), "Request should not contain jsonQuery field")
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(`{"data":{"result":"success"}}`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
qe := &QueryExecutor{
|
|
BaseClient: client,
|
|
Query: compiledQuery.JsonQuery,
|
|
Headers: map[string]interface{}{"Content-Type": "application/json; charset=utf-8"},
|
|
CacheKey: "no-cache",
|
|
Retries: false,
|
|
}
|
|
qe.endpoint = server.URL
|
|
qe.client = server.Client()
|
|
qe.cache = client.cache
|
|
|
|
result, err := qe.executeQuery()
|
|
if scenario.expectValid {
|
|
assert.NoError(err, "Unicode request should succeed")
|
|
assert.NotNil(result)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestDiagnosticLogging tests the new diagnostic logging functionality
|
|
func TestDiagnosticLogging(t *testing.T) {
|
|
assert := assertions.New(t)
|
|
|
|
t.Run("should_log_request_body_analysis", func(t *testing.T) {
|
|
// Create a server that captures and analyzes the request
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
body := make([]byte, r.ContentLength)
|
|
n, _ := r.Body.Read(body)
|
|
actualBody := body[:n]
|
|
|
|
// Verify Content-Type includes charset
|
|
contentType := r.Header.Get("Content-Type")
|
|
if !strings.Contains(contentType, "charset=utf-8") {
|
|
t.Errorf("Content-Type should include charset=utf-8, got: %s", contentType)
|
|
}
|
|
|
|
// Verify body size matches Content-Length
|
|
if int64(len(actualBody)) != r.ContentLength {
|
|
t.Errorf("Body size mismatch: Content-Length=%d, actual=%d", r.ContentLength, len(actualBody))
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(`{"data":{"result":"success"}}`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := CreateTestClient()
|
|
|
|
// Test with the production case
|
|
variables := map[string]interface{}{
|
|
"group_description": "This a group for serious buyers and sellers\nOf uber eats and Door dash account \nRippers are not allowed in this group😈",
|
|
"user_name": "2eba巴结",
|
|
}
|
|
|
|
query := "mutation storeMessage($group_description: String!, $user_name: String!) { insert_message(group_description: $group_description, user_name: $user_name) { id } }"
|
|
compiledQuery := client.compileQuery(query, variables)
|
|
|
|
qe := &QueryExecutor{
|
|
BaseClient: client,
|
|
Query: compiledQuery.JsonQuery,
|
|
Headers: map[string]interface{}{"Content-Type": "application/json; charset=utf-8"},
|
|
CacheKey: "no-cache",
|
|
Retries: false,
|
|
}
|
|
qe.endpoint = server.URL
|
|
qe.client = server.Client()
|
|
qe.cache = client.cache
|
|
|
|
result, err := qe.executeQuery()
|
|
assert.NoError(err)
|
|
assert.NotNil(result)
|
|
|
|
// The diagnostic logging should have been triggered
|
|
// We can't easily capture the logs in this test, but we can verify the request succeeded
|
|
t.Logf("Request completed successfully with diagnostic logging")
|
|
t.Logf("Request body size: %d bytes", len(compiledQuery.JsonQuery))
|
|
})
|
|
|
|
t.Run("should_detect_jsonQuery_field_inclusion", func(t *testing.T) {
|
|
// Test the validation function directly
|
|
client := CreateTestClient()
|
|
|
|
// Create a problematic JSON with jsonQuery field
|
|
problematicJSON := `{
|
|
"query": "mutation test { result }",
|
|
"variables": {"test": "value"},
|
|
"jsonQuery": "eyJxdWVyeSI6Im11dGF0aW9uIHRlc3QgeyByZXN1bHQgfSIsInZhcmlhYmxlcyI6eyJ0ZXN0IjoidmFsdWUifX0="
|
|
}`
|
|
|
|
// This should be detected by our validation
|
|
err := validateRequestBody([]byte(problematicJSON), client.Logger)
|
|
assert.Error(err, "Should detect jsonQuery field inclusion")
|
|
assert.Contains(err.Error(), "jsonQuery field found")
|
|
})
|
|
|
|
t.Run("should_validate_utf8_encoding", func(t *testing.T) {
|
|
client := CreateTestClient()
|
|
|
|
// Test valid UTF-8
|
|
validJSON := `{"query": "mutation test { result }", "variables": {"user": "2eba巴结"}}`
|
|
err := validateRequestBody([]byte(validJSON), client.Logger)
|
|
assert.NoError(err, "Valid UTF-8 should pass validation")
|
|
|
|
// Test invalid UTF-8 (manually crafted invalid sequence)
|
|
invalidUTF8 := []byte(`{"query": "mutation test { result }", "variables": {"user": "test`)
|
|
invalidUTF8 = append(invalidUTF8, 0xFF, 0xFE) // Invalid UTF-8 sequence
|
|
invalidUTF8 = append(invalidUTF8, []byte(`"}}`)...)
|
|
|
|
err = validateRequestBody(invalidUTF8, client.Logger)
|
|
assert.Error(err, "Invalid UTF-8 should fail validation")
|
|
assert.Contains(err.Error(), "invalid UTF-8")
|
|
})
|
|
|
|
t.Run("should_detect_null_bytes", func(t *testing.T) {
|
|
client := CreateTestClient()
|
|
|
|
// Test JSON with null bytes
|
|
jsonWithNulls := []byte(`{"query": "mutation test { result }", "variables": {"user": "test`)
|
|
jsonWithNulls = append(jsonWithNulls, 0x00) // Null byte
|
|
jsonWithNulls = append(jsonWithNulls, []byte(`"}}`)...)
|
|
|
|
err := validateRequestBody(jsonWithNulls, client.Logger)
|
|
assert.Error(err, "JSON with null bytes should fail validation")
|
|
assert.Contains(err.Error(), "null bytes")
|
|
})
|
|
|
|
t.Run("should_test_exact_production_size_scenario", func(t *testing.T) {
|
|
client := CreateTestClient()
|
|
|
|
// Test the exact scenario that produces ~1093 bytes
|
|
variables := map[string]interface{}{
|
|
"group_description": "This a group for serious buyers and sellers\nOf uber eats and Door dash account \nRippers are not allowed in this group😈",
|
|
"user_name": "2eba巴结",
|
|
}
|
|
|
|
// Test with a longer query that might reach 1093 bytes
|
|
longQuery := `mutation storeMessage(
|
|
$group_description: String!,
|
|
$user_name: String!,
|
|
$additional_metadata: String,
|
|
$client_info: String,
|
|
$request_timestamp: String,
|
|
$platform_data: String
|
|
) {
|
|
insert_message(
|
|
group_description: $group_description,
|
|
user_name: $user_name,
|
|
additional_metadata: $additional_metadata,
|
|
client_info: $client_info,
|
|
request_timestamp: $request_timestamp,
|
|
platform_data: $platform_data
|
|
) {
|
|
id
|
|
created_at
|
|
updated_at
|
|
status
|
|
metadata {
|
|
source
|
|
platform
|
|
version
|
|
client_info
|
|
request_id
|
|
processing_time
|
|
}
|
|
user_info {
|
|
name
|
|
id
|
|
permissions
|
|
}
|
|
}
|
|
}`
|
|
|
|
// Add additional variables to increase size
|
|
extendedVariables := make(map[string]interface{})
|
|
for k, v := range variables {
|
|
extendedVariables[k] = v
|
|
}
|
|
extendedVariables["additional_metadata"] = `{"client": "web", "version": "1.0.0", "features": ["unicode", "emoji"]}`
|
|
extendedVariables["client_info"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
|
extendedVariables["request_timestamp"] = "2023-05-23T18:01:37.000Z"
|
|
extendedVariables["platform_data"] = `{"os": "windows", "browser": "chrome", "screen": "1920x1080"}`
|
|
|
|
compiledQuery := client.compileQuery(longQuery, extendedVariables)
|
|
size := len(compiledQuery.JsonQuery)
|
|
|
|
t.Logf("Extended query size: %d bytes", size)
|
|
|
|
if size >= 1090 && size <= 1100 {
|
|
t.Logf("🎯 FOUND SIZE MATCH: %d bytes (target: 1093)", size)
|
|
|
|
// Validate this doesn't contain jsonQuery field
|
|
assert.False(bytes.Contains(compiledQuery.JsonQuery, []byte(`"jsonQuery"`)), "Should not contain jsonQuery field")
|
|
assert.True(json.Valid(compiledQuery.JsonQuery), "Should be valid JSON")
|
|
assert.True(utf8.Valid(compiledQuery.JsonQuery), "Should be valid UTF-8")
|
|
}
|
|
|
|
// Test with server to ensure no trailing garbage
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
body := make([]byte, r.ContentLength)
|
|
n, _ := r.Body.Read(body)
|
|
actualBody := body[:n]
|
|
|
|
// Strict validation
|
|
decoder := json.NewDecoder(bytes.NewReader(actualBody))
|
|
var request map[string]interface{}
|
|
err := decoder.Decode(&request)
|
|
if err != nil {
|
|
t.Errorf("JSON decode error: %s", err.Error())
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Check for trailing data
|
|
remaining := decoder.Buffered()
|
|
if remaining != nil {
|
|
buf := make([]byte, 10)
|
|
n, _ := remaining.Read(buf)
|
|
if n > 0 {
|
|
t.Errorf("Found trailing data: %s", string(buf[:n]))
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(`{"data":{"result":"success"}}`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
qe := &QueryExecutor{
|
|
BaseClient: client,
|
|
Query: compiledQuery.JsonQuery,
|
|
Headers: map[string]interface{}{"Content-Type": "application/json; charset=utf-8"},
|
|
CacheKey: "no-cache",
|
|
Retries: false,
|
|
}
|
|
qe.endpoint = server.URL
|
|
qe.client = server.Client()
|
|
qe.cache = client.cache
|
|
|
|
result, err := qe.executeQuery()
|
|
assert.NoError(err, "Extended query should succeed without trailing garbage")
|
|
assert.NotNil(result)
|
|
})
|
|
|
|
t.Run("should_demonstrate_diagnostic_logging_output", func(t *testing.T) {
|
|
// This test specifically demonstrates the diagnostic logging in action
|
|
client := CreateTestClient()
|
|
|
|
// Use the exact production case
|
|
variables := map[string]interface{}{
|
|
"group_description": "This a group for serious buyers and sellers\nOf uber eats and Door dash account \nRippers are not allowed in this group😈",
|
|
"user_name": "2eba巴结",
|
|
}
|
|
|
|
query := "mutation storeMessage($group_description: String!, $user_name: String!) { insert_message(group_description: $group_description, user_name: $user_name) { id } }"
|
|
compiledQuery := client.compileQuery(query, variables)
|
|
|
|
// Create a server that will receive the request with diagnostic logging
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
t.Logf("Server received request with Content-Type: %s", r.Header.Get("Content-Type"))
|
|
t.Logf("Server received Content-Length: %d", r.ContentLength)
|
|
|
|
body := make([]byte, r.ContentLength)
|
|
n, _ := r.Body.Read(body)
|
|
actualBody := body[:n]
|
|
|
|
t.Logf("Server read %d bytes", len(actualBody))
|
|
t.Logf("Request body first 100 chars: %s", string(actualBody[:min(100, len(actualBody))]))
|
|
t.Logf("Request body last 50 chars: %s", string(actualBody[max(0, len(actualBody)-50):]))
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write([]byte(`{"data":{"result":"success"}}`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
qe := &QueryExecutor{
|
|
BaseClient: client,
|
|
Query: compiledQuery.JsonQuery,
|
|
Headers: map[string]interface{}{"Content-Type": "application/json; charset=utf-8"},
|
|
CacheKey: "no-cache",
|
|
Retries: false,
|
|
}
|
|
qe.endpoint = server.URL
|
|
qe.client = server.Client()
|
|
qe.cache = client.cache
|
|
|
|
t.Logf("About to execute query with diagnostic logging enabled...")
|
|
result, err := qe.executeQuery()
|
|
assert.NoError(err)
|
|
assert.NotNil(result)
|
|
t.Logf("Query executed successfully with full diagnostic logging")
|
|
})
|
|
}
|