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.
		
		
		
		
		
			
		
			
				
					
					
						
							411 lines
						
					
					
						
							14 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							411 lines
						
					
					
						
							14 KiB
						
					
					
				
								package schema
							 | 
						|
								
							 | 
						|
								import (
							 | 
						|
									"strings"
							 | 
						|
									"testing"
							 | 
						|
								
							 | 
						|
									"github.com/stretchr/testify/assert"
							 | 
						|
									"github.com/stretchr/testify/require"
							 | 
						|
									"google.golang.org/protobuf/proto"
							 | 
						|
									"google.golang.org/protobuf/types/descriptorpb"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								// TestProtobufDescriptorParser_BasicParsing tests basic descriptor parsing functionality
							 | 
						|
								func TestProtobufDescriptorParser_BasicParsing(t *testing.T) {
							 | 
						|
									parser := NewProtobufDescriptorParser()
							 | 
						|
								
							 | 
						|
									t.Run("Parse Simple Message Descriptor", func(t *testing.T) {
							 | 
						|
										// Create a simple FileDescriptorSet for testing
							 | 
						|
										fds := createTestFileDescriptorSet(t, "TestMessage", []TestField{
							 | 
						|
											{Name: "id", Number: 1, Type: descriptorpb.FieldDescriptorProto_TYPE_INT32, Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL},
							 | 
						|
											{Name: "name", Number: 2, Type: descriptorpb.FieldDescriptorProto_TYPE_STRING, Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL},
							 | 
						|
										})
							 | 
						|
								
							 | 
						|
										binaryData, err := proto.Marshal(fds)
							 | 
						|
										require.NoError(t, err)
							 | 
						|
								
							 | 
						|
										// Parse the descriptor
							 | 
						|
										schema, err := parser.ParseBinaryDescriptor(binaryData, "TestMessage")
							 | 
						|
								
							 | 
						|
										// Phase E3: Descriptor resolution now works!
							 | 
						|
										if err != nil {
							 | 
						|
											// If it fails, it should be due to remaining implementation issues
							 | 
						|
											assert.True(t,
							 | 
						|
												strings.Contains(err.Error(), "message descriptor resolution not fully implemented") ||
							 | 
						|
													strings.Contains(err.Error(), "failed to build file descriptor"),
							 | 
						|
												"Expected descriptor resolution error, got: %s", err.Error())
							 | 
						|
										} else {
							 | 
						|
											// Success! Descriptor resolution is working
							 | 
						|
											assert.NotNil(t, schema)
							 | 
						|
											assert.NotNil(t, schema.MessageDescriptor)
							 | 
						|
											assert.Equal(t, "TestMessage", schema.MessageName)
							 | 
						|
											t.Log("Simple message descriptor resolution succeeded - Phase E3 is working!")
							 | 
						|
										}
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									t.Run("Parse Complex Message Descriptor", func(t *testing.T) {
							 | 
						|
										// Create a more complex FileDescriptorSet
							 | 
						|
										fds := createTestFileDescriptorSet(t, "ComplexMessage", []TestField{
							 | 
						|
											{Name: "user_id", Number: 1, Type: descriptorpb.FieldDescriptorProto_TYPE_STRING, Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL},
							 | 
						|
											{Name: "metadata", Number: 2, Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE, TypeName: "Metadata", Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL},
							 | 
						|
											{Name: "tags", Number: 3, Type: descriptorpb.FieldDescriptorProto_TYPE_STRING, Label: descriptorpb.FieldDescriptorProto_LABEL_REPEATED},
							 | 
						|
										})
							 | 
						|
								
							 | 
						|
										binaryData, err := proto.Marshal(fds)
							 | 
						|
										require.NoError(t, err)
							 | 
						|
								
							 | 
						|
										// Parse the descriptor
							 | 
						|
										schema, err := parser.ParseBinaryDescriptor(binaryData, "ComplexMessage")
							 | 
						|
								
							 | 
						|
										// Phase E3: May succeed or fail depending on message type resolution
							 | 
						|
										if err != nil {
							 | 
						|
											// If it fails, it should be due to unresolved message types (Metadata)
							 | 
						|
											assert.True(t,
							 | 
						|
												strings.Contains(err.Error(), "failed to build file descriptor") ||
							 | 
						|
													strings.Contains(err.Error(), "not found") ||
							 | 
						|
													strings.Contains(err.Error(), "cannot resolve type"),
							 | 
						|
												"Expected type resolution error, got: %s", err.Error())
							 | 
						|
										} else {
							 | 
						|
											// Success! Complex descriptor resolution is working
							 | 
						|
											assert.NotNil(t, schema)
							 | 
						|
											assert.NotNil(t, schema.MessageDescriptor)
							 | 
						|
											assert.Equal(t, "ComplexMessage", schema.MessageName)
							 | 
						|
											t.Log("Complex message descriptor resolution succeeded - Phase E3 is working!")
							 | 
						|
										}
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									t.Run("Cache Functionality", func(t *testing.T) {
							 | 
						|
										// Create a fresh parser for this test to avoid interference
							 | 
						|
										freshParser := NewProtobufDescriptorParser()
							 | 
						|
								
							 | 
						|
										fds := createTestFileDescriptorSet(t, "CacheTest", []TestField{
							 | 
						|
											{Name: "value", Number: 1, Type: descriptorpb.FieldDescriptorProto_TYPE_STRING, Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL},
							 | 
						|
										})
							 | 
						|
								
							 | 
						|
										binaryData, err := proto.Marshal(fds)
							 | 
						|
										require.NoError(t, err)
							 | 
						|
								
							 | 
						|
										// First parse
							 | 
						|
										schema1, err1 := freshParser.ParseBinaryDescriptor(binaryData, "CacheTest")
							 | 
						|
								
							 | 
						|
										// Second parse (should use cache)
							 | 
						|
										schema2, err2 := freshParser.ParseBinaryDescriptor(binaryData, "CacheTest")
							 | 
						|
								
							 | 
						|
										// Both should have the same result (success or failure)
							 | 
						|
										assert.Equal(t, err1 == nil, err2 == nil, "Both calls should have same success/failure status")
							 | 
						|
								
							 | 
						|
										if err1 == nil && err2 == nil {
							 | 
						|
											// Success case - both schemas should be identical (from cache)
							 | 
						|
											assert.Equal(t, schema1, schema2, "Cached schema should be identical")
							 | 
						|
											assert.NotNil(t, schema1.MessageDescriptor)
							 | 
						|
											t.Log("Cache functionality working with successful descriptor resolution!")
							 | 
						|
										} else {
							 | 
						|
											// Error case - errors should be identical (indicating cache usage)
							 | 
						|
											assert.Equal(t, err1.Error(), err2.Error(), "Cached errors should be identical")
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Check cache stats - should be 1 since descriptor was cached
							 | 
						|
										stats := freshParser.GetCacheStats()
							 | 
						|
										assert.Equal(t, 1, stats["cached_descriptors"])
							 | 
						|
									})
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestProtobufDescriptorParser_Validation tests descriptor validation
							 | 
						|
								func TestProtobufDescriptorParser_Validation(t *testing.T) {
							 | 
						|
									parser := NewProtobufDescriptorParser()
							 | 
						|
								
							 | 
						|
									t.Run("Invalid Binary Data", func(t *testing.T) {
							 | 
						|
										invalidData := []byte("not a protobuf descriptor")
							 | 
						|
								
							 | 
						|
										_, err := parser.ParseBinaryDescriptor(invalidData, "TestMessage")
							 | 
						|
										assert.Error(t, err)
							 | 
						|
										assert.Contains(t, err.Error(), "failed to unmarshal FileDescriptorSet")
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									t.Run("Empty FileDescriptorSet", func(t *testing.T) {
							 | 
						|
										emptyFds := &descriptorpb.FileDescriptorSet{
							 | 
						|
											File: []*descriptorpb.FileDescriptorProto{},
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										binaryData, err := proto.Marshal(emptyFds)
							 | 
						|
										require.NoError(t, err)
							 | 
						|
								
							 | 
						|
										_, err = parser.ParseBinaryDescriptor(binaryData, "TestMessage")
							 | 
						|
										assert.Error(t, err)
							 | 
						|
										assert.Contains(t, err.Error(), "FileDescriptorSet contains no files")
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									t.Run("FileDescriptor Without Name", func(t *testing.T) {
							 | 
						|
										invalidFds := &descriptorpb.FileDescriptorSet{
							 | 
						|
											File: []*descriptorpb.FileDescriptorProto{
							 | 
						|
												{
							 | 
						|
													// Missing Name field
							 | 
						|
													Package: proto.String("test.package"),
							 | 
						|
												},
							 | 
						|
											},
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										binaryData, err := proto.Marshal(invalidFds)
							 | 
						|
										require.NoError(t, err)
							 | 
						|
								
							 | 
						|
										_, err = parser.ParseBinaryDescriptor(binaryData, "TestMessage")
							 | 
						|
										assert.Error(t, err)
							 | 
						|
										assert.Contains(t, err.Error(), "file descriptor 0 has no name")
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									t.Run("FileDescriptor Without Package", func(t *testing.T) {
							 | 
						|
										invalidFds := &descriptorpb.FileDescriptorSet{
							 | 
						|
											File: []*descriptorpb.FileDescriptorProto{
							 | 
						|
												{
							 | 
						|
													Name: proto.String("test.proto"),
							 | 
						|
													// Missing Package field
							 | 
						|
												},
							 | 
						|
											},
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										binaryData, err := proto.Marshal(invalidFds)
							 | 
						|
										require.NoError(t, err)
							 | 
						|
								
							 | 
						|
										_, err = parser.ParseBinaryDescriptor(binaryData, "TestMessage")
							 | 
						|
										assert.Error(t, err)
							 | 
						|
										assert.Contains(t, err.Error(), "file descriptor test.proto has no package")
							 | 
						|
									})
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestProtobufDescriptorParser_MessageSearch tests message finding functionality
							 | 
						|
								func TestProtobufDescriptorParser_MessageSearch(t *testing.T) {
							 | 
						|
									parser := NewProtobufDescriptorParser()
							 | 
						|
								
							 | 
						|
									t.Run("Message Not Found", func(t *testing.T) {
							 | 
						|
										fds := createTestFileDescriptorSet(t, "ExistingMessage", []TestField{
							 | 
						|
											{Name: "field1", Number: 1, Type: descriptorpb.FieldDescriptorProto_TYPE_STRING, Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL},
							 | 
						|
										})
							 | 
						|
								
							 | 
						|
										binaryData, err := proto.Marshal(fds)
							 | 
						|
										require.NoError(t, err)
							 | 
						|
								
							 | 
						|
										_, err = parser.ParseBinaryDescriptor(binaryData, "NonExistentMessage")
							 | 
						|
										assert.Error(t, err)
							 | 
						|
										assert.Contains(t, err.Error(), "message NonExistentMessage not found")
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									t.Run("Nested Message Search", func(t *testing.T) {
							 | 
						|
										// Create FileDescriptorSet with nested messages
							 | 
						|
										fds := &descriptorpb.FileDescriptorSet{
							 | 
						|
											File: []*descriptorpb.FileDescriptorProto{
							 | 
						|
												{
							 | 
						|
													Name:    proto.String("test.proto"),
							 | 
						|
													Package: proto.String("test.package"),
							 | 
						|
													MessageType: []*descriptorpb.DescriptorProto{
							 | 
						|
														{
							 | 
						|
															Name: proto.String("OuterMessage"),
							 | 
						|
															NestedType: []*descriptorpb.DescriptorProto{
							 | 
						|
																{
							 | 
						|
																	Name: proto.String("NestedMessage"),
							 | 
						|
																	Field: []*descriptorpb.FieldDescriptorProto{
							 | 
						|
																		{
							 | 
						|
																			Name:   proto.String("nested_field"),
							 | 
						|
																			Number: proto.Int32(1),
							 | 
						|
																			Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
							 | 
						|
																			Label:  descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(),
							 | 
						|
																		},
							 | 
						|
																	},
							 | 
						|
																},
							 | 
						|
															},
							 | 
						|
														},
							 | 
						|
													},
							 | 
						|
												},
							 | 
						|
											},
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										binaryData, err := proto.Marshal(fds)
							 | 
						|
										require.NoError(t, err)
							 | 
						|
								
							 | 
						|
										_, err = parser.ParseBinaryDescriptor(binaryData, "NestedMessage")
							 | 
						|
										// Nested message search now works! May succeed or fail on descriptor building
							 | 
						|
										if err != nil {
							 | 
						|
											// If it fails, it should be due to descriptor building issues
							 | 
						|
											assert.True(t,
							 | 
						|
												strings.Contains(err.Error(), "failed to build file descriptor") ||
							 | 
						|
													strings.Contains(err.Error(), "invalid cardinality") ||
							 | 
						|
													strings.Contains(err.Error(), "nested message descriptor resolution not fully implemented"),
							 | 
						|
												"Expected descriptor building error, got: %s", err.Error())
							 | 
						|
										} else {
							 | 
						|
											// Success! Nested message resolution is working
							 | 
						|
											t.Log("Nested message resolution succeeded - Phase E3 is working!")
							 | 
						|
										}
							 | 
						|
									})
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestProtobufDescriptorParser_Dependencies tests dependency extraction
							 | 
						|
								func TestProtobufDescriptorParser_Dependencies(t *testing.T) {
							 | 
						|
									parser := NewProtobufDescriptorParser()
							 | 
						|
								
							 | 
						|
									t.Run("Extract Dependencies", func(t *testing.T) {
							 | 
						|
										// Create FileDescriptorSet with dependencies
							 | 
						|
										fds := &descriptorpb.FileDescriptorSet{
							 | 
						|
											File: []*descriptorpb.FileDescriptorProto{
							 | 
						|
												{
							 | 
						|
													Name:    proto.String("main.proto"),
							 | 
						|
													Package: proto.String("main.package"),
							 | 
						|
													Dependency: []string{
							 | 
						|
														"google/protobuf/timestamp.proto",
							 | 
						|
														"common/types.proto",
							 | 
						|
													},
							 | 
						|
													MessageType: []*descriptorpb.DescriptorProto{
							 | 
						|
														{
							 | 
						|
															Name: proto.String("MainMessage"),
							 | 
						|
															Field: []*descriptorpb.FieldDescriptorProto{
							 | 
						|
																{
							 | 
						|
																	Name:   proto.String("id"),
							 | 
						|
																	Number: proto.Int32(1),
							 | 
						|
																	Type:   descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum(),
							 | 
						|
																},
							 | 
						|
															},
							 | 
						|
														},
							 | 
						|
													},
							 | 
						|
												},
							 | 
						|
											},
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										_, err := proto.Marshal(fds)
							 | 
						|
										require.NoError(t, err)
							 | 
						|
								
							 | 
						|
										// Parse and check dependencies (even though parsing fails, we can test dependency extraction)
							 | 
						|
										dependencies := parser.extractDependencies(fds)
							 | 
						|
										assert.Len(t, dependencies, 2)
							 | 
						|
										assert.Contains(t, dependencies, "google/protobuf/timestamp.proto")
							 | 
						|
										assert.Contains(t, dependencies, "common/types.proto")
							 | 
						|
									})
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestProtobufSchema_Methods tests ProtobufSchema methods
							 | 
						|
								func TestProtobufSchema_Methods(t *testing.T) {
							 | 
						|
									// Create a basic schema for testing
							 | 
						|
									fds := createTestFileDescriptorSet(t, "TestSchema", []TestField{
							 | 
						|
										{Name: "field1", Number: 1, Type: descriptorpb.FieldDescriptorProto_TYPE_STRING, Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL},
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									schema := &ProtobufSchema{
							 | 
						|
										FileDescriptorSet: fds,
							 | 
						|
										MessageDescriptor: nil, // Not implemented in Phase E1
							 | 
						|
										MessageName:       "TestSchema",
							 | 
						|
										PackageName:       "test.package",
							 | 
						|
										Dependencies:      []string{"common.proto"},
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									t.Run("GetMessageFields Implemented", func(t *testing.T) {
							 | 
						|
										fields, err := schema.GetMessageFields()
							 | 
						|
										assert.NoError(t, err)
							 | 
						|
										assert.Len(t, fields, 1)
							 | 
						|
										assert.Equal(t, "field1", fields[0].Name)
							 | 
						|
										assert.Equal(t, int32(1), fields[0].Number)
							 | 
						|
										assert.Equal(t, "string", fields[0].Type)
							 | 
						|
										assert.Equal(t, "optional", fields[0].Label)
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									t.Run("GetFieldByName Implemented", func(t *testing.T) {
							 | 
						|
										field, err := schema.GetFieldByName("field1")
							 | 
						|
										assert.NoError(t, err)
							 | 
						|
										assert.Equal(t, "field1", field.Name)
							 | 
						|
										assert.Equal(t, int32(1), field.Number)
							 | 
						|
										assert.Equal(t, "string", field.Type)
							 | 
						|
										assert.Equal(t, "optional", field.Label)
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									t.Run("GetFieldByNumber Implemented", func(t *testing.T) {
							 | 
						|
										field, err := schema.GetFieldByNumber(1)
							 | 
						|
										assert.NoError(t, err)
							 | 
						|
										assert.Equal(t, "field1", field.Name)
							 | 
						|
										assert.Equal(t, int32(1), field.Number)
							 | 
						|
										assert.Equal(t, "string", field.Type)
							 | 
						|
										assert.Equal(t, "optional", field.Label)
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									t.Run("ValidateMessage Requires MessageDescriptor", func(t *testing.T) {
							 | 
						|
										err := schema.ValidateMessage([]byte("test message"))
							 | 
						|
										assert.Error(t, err)
							 | 
						|
										assert.Contains(t, err.Error(), "no message descriptor available for validation")
							 | 
						|
									})
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestProtobufDescriptorParser_CacheManagement tests cache management
							 | 
						|
								func TestProtobufDescriptorParser_CacheManagement(t *testing.T) {
							 | 
						|
									parser := NewProtobufDescriptorParser()
							 | 
						|
								
							 | 
						|
									// Add some entries to cache
							 | 
						|
									fds1 := createTestFileDescriptorSet(t, "Message1", []TestField{
							 | 
						|
										{Name: "field1", Number: 1, Type: descriptorpb.FieldDescriptorProto_TYPE_STRING, Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL},
							 | 
						|
									})
							 | 
						|
									fds2 := createTestFileDescriptorSet(t, "Message2", []TestField{
							 | 
						|
										{Name: "field2", Number: 1, Type: descriptorpb.FieldDescriptorProto_TYPE_INT32, Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL},
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									binaryData1, _ := proto.Marshal(fds1)
							 | 
						|
									binaryData2, _ := proto.Marshal(fds2)
							 | 
						|
								
							 | 
						|
									// Parse both (will fail but add to cache)
							 | 
						|
									parser.ParseBinaryDescriptor(binaryData1, "Message1")
							 | 
						|
									parser.ParseBinaryDescriptor(binaryData2, "Message2")
							 | 
						|
								
							 | 
						|
									// Check cache has entries (descriptors cached even though resolution failed)
							 | 
						|
									stats := parser.GetCacheStats()
							 | 
						|
									assert.Equal(t, 2, stats["cached_descriptors"])
							 | 
						|
								
							 | 
						|
									// Clear cache
							 | 
						|
									parser.ClearCache()
							 | 
						|
								
							 | 
						|
									// Check cache is empty
							 | 
						|
									stats = parser.GetCacheStats()
							 | 
						|
									assert.Equal(t, 0, stats["cached_descriptors"])
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// Helper types and functions for testing
							 | 
						|
								
							 | 
						|
								type TestField struct {
							 | 
						|
									Name     string
							 | 
						|
									Number   int32
							 | 
						|
									Type     descriptorpb.FieldDescriptorProto_Type
							 | 
						|
									Label    descriptorpb.FieldDescriptorProto_Label
							 | 
						|
									TypeName string
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func createTestFileDescriptorSet(t *testing.T, messageName string, fields []TestField) *descriptorpb.FileDescriptorSet {
							 | 
						|
									// Create field descriptors
							 | 
						|
									fieldDescriptors := make([]*descriptorpb.FieldDescriptorProto, len(fields))
							 | 
						|
									for i, field := range fields {
							 | 
						|
										fieldDesc := &descriptorpb.FieldDescriptorProto{
							 | 
						|
											Name:   proto.String(field.Name),
							 | 
						|
											Number: proto.Int32(field.Number),
							 | 
						|
											Type:   field.Type.Enum(),
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if field.Label != descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL {
							 | 
						|
											fieldDesc.Label = field.Label.Enum()
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if field.TypeName != "" {
							 | 
						|
											fieldDesc.TypeName = proto.String(field.TypeName)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										fieldDescriptors[i] = fieldDesc
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Create message descriptor
							 | 
						|
									messageDesc := &descriptorpb.DescriptorProto{
							 | 
						|
										Name:  proto.String(messageName),
							 | 
						|
										Field: fieldDescriptors,
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Create file descriptor
							 | 
						|
									fileDesc := &descriptorpb.FileDescriptorProto{
							 | 
						|
										Name:        proto.String("test.proto"),
							 | 
						|
										Package:     proto.String("test.package"),
							 | 
						|
										MessageType: []*descriptorpb.DescriptorProto{messageDesc},
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Create FileDescriptorSet
							 | 
						|
									return &descriptorpb.FileDescriptorSet{
							 | 
						|
										File: []*descriptorpb.FileDescriptorProto{fileDesc},
							 | 
						|
									}
							 | 
						|
								}
							 |