You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							313 lines
						
					
					
						
							9.0 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							313 lines
						
					
					
						
							9.0 KiB
						
					
					
				
								package protocol
							 | 
						|
								
							 | 
						|
								import (
							 | 
						|
									"encoding/binary"
							 | 
						|
									"testing"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								// TestResponseFormatsNoCorrelationID verifies that NO API response includes
							 | 
						|
								// the correlation ID in the response body (it should only be in the wire header)
							 | 
						|
								func TestResponseFormatsNoCorrelationID(t *testing.T) {
							 | 
						|
									tests := []struct {
							 | 
						|
										name        string
							 | 
						|
										apiKey      uint16
							 | 
						|
										apiVersion  uint16
							 | 
						|
										buildFunc   func(correlationID uint32) ([]byte, error)
							 | 
						|
										description string
							 | 
						|
									}{
							 | 
						|
										// Control Plane APIs
							 | 
						|
										{
							 | 
						|
											name:        "ApiVersions_v0",
							 | 
						|
											apiKey:      18,
							 | 
						|
											apiVersion:  0,
							 | 
						|
											description: "ApiVersions v0 should not include correlation ID in body",
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:        "ApiVersions_v4",
							 | 
						|
											apiKey:      18,
							 | 
						|
											apiVersion:  4,
							 | 
						|
											description: "ApiVersions v4 (flexible) should not include correlation ID in body",
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:        "Metadata_v0",
							 | 
						|
											apiKey:      3,
							 | 
						|
											apiVersion:  0,
							 | 
						|
											description: "Metadata v0 should not include correlation ID in body",
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:        "Metadata_v7",
							 | 
						|
											apiKey:      3,
							 | 
						|
											apiVersion:  7,
							 | 
						|
											description: "Metadata v7 should not include correlation ID in body",
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:        "FindCoordinator_v0",
							 | 
						|
											apiKey:      10,
							 | 
						|
											apiVersion:  0,
							 | 
						|
											description: "FindCoordinator v0 should not include correlation ID in body",
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:        "FindCoordinator_v2",
							 | 
						|
											apiKey:      10,
							 | 
						|
											apiVersion:  2,
							 | 
						|
											description: "FindCoordinator v2 should not include correlation ID in body",
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:        "DescribeConfigs_v0",
							 | 
						|
											apiKey:      32,
							 | 
						|
											apiVersion:  0,
							 | 
						|
											description: "DescribeConfigs v0 should not include correlation ID in body",
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:        "DescribeConfigs_v4",
							 | 
						|
											apiKey:      32,
							 | 
						|
											apiVersion:  4,
							 | 
						|
											description: "DescribeConfigs v4 (flexible) should not include correlation ID in body",
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:        "DescribeCluster_v0",
							 | 
						|
											apiKey:      60,
							 | 
						|
											apiVersion:  0,
							 | 
						|
											description: "DescribeCluster v0 (flexible) should not include correlation ID in body",
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:        "InitProducerId_v0",
							 | 
						|
											apiKey:      22,
							 | 
						|
											apiVersion:  0,
							 | 
						|
											description: "InitProducerId v0 should not include correlation ID in body",
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:        "InitProducerId_v4",
							 | 
						|
											apiKey:      22,
							 | 
						|
											apiVersion:  4,
							 | 
						|
											description: "InitProducerId v4 (flexible) should not include correlation ID in body",
							 | 
						|
										},
							 | 
						|
								
							 | 
						|
										// Consumer Group Coordination APIs
							 | 
						|
										{
							 | 
						|
											name:        "JoinGroup_v0",
							 | 
						|
											apiKey:      11,
							 | 
						|
											apiVersion:  0,
							 | 
						|
											description: "JoinGroup v0 should not include correlation ID in body",
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:        "SyncGroup_v0",
							 | 
						|
											apiKey:      14,
							 | 
						|
											apiVersion:  0,
							 | 
						|
											description: "SyncGroup v0 should not include correlation ID in body",
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:        "Heartbeat_v0",
							 | 
						|
											apiKey:      12,
							 | 
						|
											apiVersion:  0,
							 | 
						|
											description: "Heartbeat v0 should not include correlation ID in body",
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:        "LeaveGroup_v0",
							 | 
						|
											apiKey:      13,
							 | 
						|
											apiVersion:  0,
							 | 
						|
											description: "LeaveGroup v0 should not include correlation ID in body",
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:        "OffsetFetch_v0",
							 | 
						|
											apiKey:      9,
							 | 
						|
											apiVersion:  0,
							 | 
						|
											description: "OffsetFetch v0 should not include correlation ID in body",
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:        "OffsetCommit_v0",
							 | 
						|
											apiKey:      8,
							 | 
						|
											apiVersion:  0,
							 | 
						|
											description: "OffsetCommit v0 should not include correlation ID in body",
							 | 
						|
										},
							 | 
						|
								
							 | 
						|
										// Data Plane APIs
							 | 
						|
										{
							 | 
						|
											name:        "Produce_v0",
							 | 
						|
											apiKey:      0,
							 | 
						|
											apiVersion:  0,
							 | 
						|
											description: "Produce v0 should not include correlation ID in body",
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:        "Produce_v7",
							 | 
						|
											apiKey:      0,
							 | 
						|
											apiVersion:  7,
							 | 
						|
											description: "Produce v7 should not include correlation ID in body",
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:        "Fetch_v0",
							 | 
						|
											apiKey:      1,
							 | 
						|
											apiVersion:  0,
							 | 
						|
											description: "Fetch v0 should not include correlation ID in body",
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:        "Fetch_v7",
							 | 
						|
											apiKey:      1,
							 | 
						|
											apiVersion:  7,
							 | 
						|
											description: "Fetch v7 should not include correlation ID in body",
							 | 
						|
										},
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									for _, tt := range tests {
							 | 
						|
										t.Run(tt.name, func(t *testing.T) {
							 | 
						|
											t.Logf("Testing %s: %s", tt.name, tt.description)
							 | 
						|
								
							 | 
						|
											// This test documents the EXPECTATION but can't automatically verify
							 | 
						|
											// all responses without implementing mock handlers for each API.
							 | 
						|
											// The key insight is: ALL responses should be checked manually
							 | 
						|
											// or with integration tests.
							 | 
						|
								
							 | 
						|
											t.Logf("✓ API Key %d Version %d: Correlation ID should be handled by writeResponseWithHeader",
							 | 
						|
												tt.apiKey, tt.apiVersion)
							 | 
						|
										})
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestFlexibleResponseHeaderFormat verifies that flexible responses
							 | 
						|
								// include the 0x00 tagged fields byte in the header
							 | 
						|
								func TestFlexibleResponseHeaderFormat(t *testing.T) {
							 | 
						|
									tests := []struct {
							 | 
						|
										name       string
							 | 
						|
										apiKey     uint16
							 | 
						|
										apiVersion uint16
							 | 
						|
										isFlexible bool
							 | 
						|
									}{
							 | 
						|
										// ApiVersions is special - never flexible header (AdminClient compatibility)
							 | 
						|
										{"ApiVersions_v0", 18, 0, false},
							 | 
						|
										{"ApiVersions_v3", 18, 3, false}, // Special case!
							 | 
						|
										{"ApiVersions_v4", 18, 4, false}, // Special case!
							 | 
						|
								
							 | 
						|
										// Metadata becomes flexible at v9+
							 | 
						|
										{"Metadata_v0", 3, 0, false},
							 | 
						|
										{"Metadata_v7", 3, 7, false},
							 | 
						|
										{"Metadata_v9", 3, 9, true},
							 | 
						|
								
							 | 
						|
										// Produce becomes flexible at v9+
							 | 
						|
										{"Produce_v0", 0, 0, false},
							 | 
						|
										{"Produce_v7", 0, 7, false},
							 | 
						|
										{"Produce_v9", 0, 9, true},
							 | 
						|
								
							 | 
						|
										// Fetch becomes flexible at v12+
							 | 
						|
										{"Fetch_v0", 1, 0, false},
							 | 
						|
										{"Fetch_v7", 1, 7, false},
							 | 
						|
										{"Fetch_v12", 1, 12, true},
							 | 
						|
								
							 | 
						|
										// FindCoordinator becomes flexible at v3+
							 | 
						|
										{"FindCoordinator_v0", 10, 0, false},
							 | 
						|
										{"FindCoordinator_v2", 10, 2, false},
							 | 
						|
										{"FindCoordinator_v3", 10, 3, true},
							 | 
						|
								
							 | 
						|
										// JoinGroup becomes flexible at v6+
							 | 
						|
										{"JoinGroup_v0", 11, 0, false},
							 | 
						|
										{"JoinGroup_v5", 11, 5, false},
							 | 
						|
										{"JoinGroup_v6", 11, 6, true},
							 | 
						|
								
							 | 
						|
										// SyncGroup becomes flexible at v4+
							 | 
						|
										{"SyncGroup_v0", 14, 0, false},
							 | 
						|
										{"SyncGroup_v3", 14, 3, false},
							 | 
						|
										{"SyncGroup_v4", 14, 4, true},
							 | 
						|
								
							 | 
						|
										// Heartbeat becomes flexible at v4+
							 | 
						|
										{"Heartbeat_v0", 12, 0, false},
							 | 
						|
										{"Heartbeat_v3", 12, 3, false},
							 | 
						|
										{"Heartbeat_v4", 12, 4, true},
							 | 
						|
								
							 | 
						|
										// LeaveGroup becomes flexible at v4+
							 | 
						|
										{"LeaveGroup_v0", 13, 0, false},
							 | 
						|
										{"LeaveGroup_v3", 13, 3, false},
							 | 
						|
										{"LeaveGroup_v4", 13, 4, true},
							 | 
						|
								
							 | 
						|
										// OffsetFetch becomes flexible at v6+
							 | 
						|
										{"OffsetFetch_v0", 9, 0, false},
							 | 
						|
										{"OffsetFetch_v5", 9, 5, false},
							 | 
						|
										{"OffsetFetch_v6", 9, 6, true},
							 | 
						|
								
							 | 
						|
										// OffsetCommit becomes flexible at v8+
							 | 
						|
										{"OffsetCommit_v0", 8, 0, false},
							 | 
						|
										{"OffsetCommit_v7", 8, 7, false},
							 | 
						|
										{"OffsetCommit_v8", 8, 8, true},
							 | 
						|
								
							 | 
						|
										// DescribeConfigs becomes flexible at v4+
							 | 
						|
										{"DescribeConfigs_v0", 32, 0, false},
							 | 
						|
										{"DescribeConfigs_v3", 32, 3, false},
							 | 
						|
										{"DescribeConfigs_v4", 32, 4, true},
							 | 
						|
								
							 | 
						|
										// InitProducerId becomes flexible at v2+
							 | 
						|
										{"InitProducerId_v0", 22, 0, false},
							 | 
						|
										{"InitProducerId_v1", 22, 1, false},
							 | 
						|
										{"InitProducerId_v2", 22, 2, true},
							 | 
						|
								
							 | 
						|
										// DescribeCluster is always flexible
							 | 
						|
										{"DescribeCluster_v0", 60, 0, true},
							 | 
						|
										{"DescribeCluster_v1", 60, 1, true},
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									for _, tt := range tests {
							 | 
						|
										t.Run(tt.name, func(t *testing.T) {
							 | 
						|
											actual := isFlexibleResponse(tt.apiKey, tt.apiVersion)
							 | 
						|
											if actual != tt.isFlexible {
							 | 
						|
												t.Errorf("%s: isFlexibleResponse(%d, %d) = %v, want %v",
							 | 
						|
													tt.name, tt.apiKey, tt.apiVersion, actual, tt.isFlexible)
							 | 
						|
											} else {
							 | 
						|
												t.Logf("✓ %s: correctly identified as flexible=%v", tt.name, tt.isFlexible)
							 | 
						|
											}
							 | 
						|
										})
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestCorrelationIDNotInResponseBody is a helper that can be used
							 | 
						|
								// to scan response bytes and detect if correlation ID appears in the body
							 | 
						|
								func TestCorrelationIDNotInResponseBody(t *testing.T) {
							 | 
						|
									// Test helper function
							 | 
						|
									hasCorrelationIDInBody := func(responseBody []byte, correlationID uint32) bool {
							 | 
						|
										if len(responseBody) < 4 {
							 | 
						|
											return false
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Check if the first 4 bytes match the correlation ID
							 | 
						|
										actual := binary.BigEndian.Uint32(responseBody[0:4])
							 | 
						|
										return actual == correlationID
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									t.Run("DetectCorrelationIDInBody", func(t *testing.T) {
							 | 
						|
										correlationID := uint32(12345)
							 | 
						|
								
							 | 
						|
										// Case 1: Response with correlation ID (BAD)
							 | 
						|
										badResponse := make([]byte, 8)
							 | 
						|
										binary.BigEndian.PutUint32(badResponse[0:4], correlationID)
							 | 
						|
										badResponse[4] = 0x00 // some data
							 | 
						|
								
							 | 
						|
										if !hasCorrelationIDInBody(badResponse, correlationID) {
							 | 
						|
											t.Error("Failed to detect correlation ID in response body")
							 | 
						|
										} else {
							 | 
						|
											t.Log("✓ Successfully detected correlation ID in body (bad response)")
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Case 2: Response without correlation ID (GOOD)
							 | 
						|
										goodResponse := make([]byte, 8)
							 | 
						|
										goodResponse[0] = 0x00 // error code
							 | 
						|
										goodResponse[1] = 0x00
							 | 
						|
								
							 | 
						|
										if hasCorrelationIDInBody(goodResponse, correlationID) {
							 | 
						|
											t.Error("False positive: detected correlation ID when it's not there")
							 | 
						|
										} else {
							 | 
						|
											t.Log("✓ Correctly identified response without correlation ID")
							 | 
						|
										}
							 | 
						|
									})
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestWireProtocolFormat documents the expected wire format
							 | 
						|
								func TestWireProtocolFormat(t *testing.T) {
							 | 
						|
									t.Log("Kafka Wire Protocol Format (KIP-482):")
							 | 
						|
									t.Log("  Non-flexible responses:")
							 | 
						|
									t.Log("    [Size: 4 bytes][Correlation ID: 4 bytes][Response Body]")
							 | 
						|
									t.Log("")
							 | 
						|
									t.Log("  Flexible responses (header version 1+):")
							 | 
						|
									t.Log("    [Size: 4 bytes][Correlation ID: 4 bytes][Tagged Fields: 1+ bytes][Response Body]")
							 | 
						|
									t.Log("")
							 | 
						|
									t.Log("  Size field: includes correlation ID + tagged fields + body")
							 | 
						|
									t.Log("  Tagged Fields: varint-encoded, 0x00 for empty")
							 | 
						|
									t.Log("")
							 | 
						|
									t.Log("CRITICAL: Response body should NEVER include correlation ID!")
							 | 
						|
									t.Log("          It is written ONLY by writeResponseWithHeader")
							 | 
						|
								}
							 |