Skip to main content
Glama
resolver_fqn_test.go8.72 kB
package proto import ( "os" "path/filepath" "testing" ) // TestResolveFullyQualifiedTypes tests that the resolver correctly handles // fully qualified type names and resolves to the correct message when multiple // messages with the same simple name exist func TestResolveFullyQualifiedTypes(t *testing.T) { index := NewProtoIndex(testLogger()) // Create two different Price messages in different packages // This simulates the real issue where there are multiple Price messages priceV1 := &ProtoMessage{ Name: "Price", FullName: "acme.dto.payments.checkout_orchestrator.v1beta1.Price", Fields: []ProtoField{ {Name: "amount", Type: "string", Number: 1}, {Name: "currency_code", Type: "string", Number: 2}, }, } priceV2 := &ProtoMessage{ Name: "Price", FullName: "com.payments.common.Price", Fields: []ProtoField{ {Name: "iso_currency_code", Type: "string", Number: 1}, {Name: "amount", Type: "uint64", Number: 2}, {Name: "precision", Type: "uint64", Number: 3}, {Name: "iso_country_code", Type: "string", Number: 4}, }, } productRef := &ProtoMessage{ Name: "ProductReference", FullName: "acme.dto.payments.checkout_orchestrator.v1beta1.ProductReference", Fields: []ProtoField{ {Name: "product_id", Type: "int64", Number: 1}, {Name: "product_type", Type: "string", Number: 2}, }, } // TaxableLine uses the fully qualified type name taxableLine := &ProtoMessage{ Name: "TaxableLine", FullName: "acme.rpc.payments.checkout_orchestrator.info.v1beta1.TaxableLine", Fields: []ProtoField{ {Name: "product_reference", Type: "acme.dto.payments.checkout_orchestrator.v1beta1.ProductReference", Number: 1}, {Name: "unit_net_price", Type: "acme.dto.payments.checkout_orchestrator.v1beta1.Price", Number: 2}, {Name: "quantity", Type: "int64", Number: 3}, }, } // Index all messages index.messages["acme.dto.payments.checkout_orchestrator.v1beta1.Price"] = priceV1 index.messages["com.payments.common.Price"] = priceV2 index.messages["acme.dto.payments.checkout_orchestrator.v1beta1.ProductReference"] = productRef index.messages["acme.rpc.payments.checkout_orchestrator.info.v1beta1.TaxableLine"] = taxableLine // Resolve types for TaxableLine resolved := index.resolveMessageTypes(taxableLine, 10, nil) // Should resolve ProductReference and the correct Price (v1beta1, not common) if len(resolved) != 2 { t.Errorf("resolveMessageTypes() resolved %d types, want 2", len(resolved)) for k := range resolved { t.Logf(" Resolved: %s", k) } } // Check ProductReference is resolved productRefKey := "acme.dto.payments.checkout_orchestrator.v1beta1.ProductReference" if _, ok := resolved[productRefKey]; !ok { t.Errorf("resolveMessageTypes() did not resolve %s", productRefKey) } // Check the correct Price is resolved (v1beta1, not common) priceKey := "acme.dto.payments.checkout_orchestrator.v1beta1.Price" priceResolved, ok := resolved[priceKey] if !ok { t.Errorf("resolveMessageTypes() did not resolve %s", priceKey) t.Logf("Available keys:") for k := range resolved { t.Logf(" %s", k) } t.FailNow() } // Verify it's the correct Price message with amount (string) and currency_code priceMap, ok := priceResolved.(map[string]interface{}) if !ok { t.Fatal("Price is not a map[string]interface{}") } if fullName := priceMap["full_name"]; fullName != "acme.dto.payments.checkout_orchestrator.v1beta1.Price" { t.Errorf("Price full_name = %v, want 'acme.dto.payments.checkout_orchestrator.v1beta1.Price'", fullName) } // Check fields - should have amount (string) and currency_code, NOT iso_currency_code, precision, etc. fields, ok := priceMap["fields"].([]map[string]interface{}) if !ok { t.Fatal("Price fields is not a []map[string]interface{}") } if len(fields) != 2 { t.Errorf("Price has %d fields, want 2 (amount, currency_code)", len(fields)) } fieldNames := make(map[string]string) for _, field := range fields { name := field["name"].(string) fieldType := field["type"].(string) fieldNames[name] = fieldType } // Verify correct fields if fieldType, ok := fieldNames["amount"]; !ok { t.Error("Price is missing 'amount' field") } else if fieldType != "string" { t.Errorf("Price.amount type = %s, want 'string'", fieldType) } if _, ok := fieldNames["currency_code"]; !ok { t.Error("Price is missing 'currency_code' field") } // Verify wrong fields are NOT present if _, ok := fieldNames["iso_currency_code"]; ok { t.Error("Price should NOT have 'iso_currency_code' field (wrong Price message resolved)") } if _, ok := fieldNames["precision"]; ok { t.Error("Price should NOT have 'precision' field (wrong Price message resolved)") } if _, ok := fieldNames["iso_country_code"]; ok { t.Error("Price should NOT have 'iso_country_code' field (wrong Price message resolved)") } } // TestResolveFullyQualifiedTypesWithParser tests the full end-to-end flow // from parsing a proto file with fully qualified types to resolving them func TestResolveFullyQualifiedTypesWithParser(t *testing.T) { index := NewProtoIndex(testLogger()) // Create temporary proto files tempDir := t.TempDir() // Create the correct Price message priceProto := filepath.Join(tempDir, "price_v1.proto") priceContent := `syntax = "proto3"; package acme.dto.payments.checkout_orchestrator.v1beta1; message Price { string amount = 1; string currency_code = 2; } message ProductReference { int64 product_id = 1; string product_type = 2; } ` if err := os.WriteFile(priceProto, []byte(priceContent), 0644); err != nil { t.Fatalf("Failed to write price proto file: %v", err) } // Create the wrong Price message (different package) wrongPriceProto := filepath.Join(tempDir, "price_common.proto") wrongPriceContent := `syntax = "proto3"; package com.payments.common; message Price { string iso_currency_code = 1; uint64 amount = 2; uint64 precision = 3; string iso_country_code = 4; } ` if err := os.WriteFile(wrongPriceProto, []byte(wrongPriceContent), 0644); err != nil { t.Fatalf("Failed to write wrong price proto file: %v", err) } // Create TaxableLine that references the correct Price with fully qualified name taxableProto := filepath.Join(tempDir, "taxable.proto") taxableContent := `syntax = "proto3"; package acme.rpc.payments.checkout_orchestrator.info.v1beta1; message TaxableLine { acme.dto.payments.checkout_orchestrator.v1beta1.ProductReference product_reference = 1; acme.dto.payments.checkout_orchestrator.v1beta1.Price unit_net_price = 2; int64 quantity = 3; } ` if err := os.WriteFile(taxableProto, []byte(taxableContent), 0644); err != nil { t.Fatalf("Failed to write taxable proto file: %v", err) } // Index all proto files count, err := index.IndexDirectory(tempDir) if err != nil { t.Fatalf("IndexDirectory() error = %v", err) } if count != 3 { t.Logf("Warning: indexed %d files, expected 3", count) } // Get TaxableLine message with type resolution result, err := index.GetMessage("TaxableLine", true, 10) if err != nil { t.Fatalf("GetMessage() error = %v", err) } resolvedTypes, ok := result["resolved_types"] if !ok { t.Fatal("GetMessage() should have resolved_types") } resolvedMap := resolvedTypes.(map[string]interface{}) // Check that the correct Price is resolved priceKey := "acme.dto.payments.checkout_orchestrator.v1beta1.Price" priceResolved, ok := resolvedMap[priceKey] if !ok { t.Errorf("GetMessage() did not resolve %s", priceKey) t.Logf("Available keys:") for k := range resolvedMap { t.Logf(" %s", k) } t.FailNow() } priceMap := priceResolved.(map[string]interface{}) // Verify it's the correct Price if fullName := priceMap["full_name"]; fullName != "acme.dto.payments.checkout_orchestrator.v1beta1.Price" { t.Errorf("Price full_name = %v, want 'acme.dto.payments.checkout_orchestrator.v1beta1.Price'", fullName) } fields := priceMap["fields"].([]map[string]interface{}) if len(fields) != 2 { t.Errorf("Price has %d fields, want 2", len(fields)) } // Verify we got the correct Price with string amount and currency_code fieldNames := make(map[string]string) for _, field := range fields { name := field["name"].(string) fieldType := field["type"].(string) fieldNames[name] = fieldType } if fieldType, ok := fieldNames["amount"]; !ok || fieldType != "string" { t.Errorf("Price.amount type = %s, want 'string'", fieldType) } if _, ok := fieldNames["currency_code"]; !ok { t.Error("Price is missing 'currency_code' field") } // Verify we didn't get the wrong Price if _, ok := fieldNames["iso_currency_code"]; ok { t.Error("Resolved wrong Price message (has iso_currency_code field)") } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/umuterturk/mcp-proto'

If you have feedback or need assistance with the MCP directory API, please join our Discord server