@@ -1414,6 +1414,114 @@ func TestGenAndCommentHelpers(t *testing.T) {
14141414 }
14151415}
14161416
1417+ func TestInsertDynamicRequestHeaders_Ordering (t * testing.T ) {
1418+ g := & generator {
1419+ imports : make (map [pbinfo.ImportSpec ]bool ),
1420+ descInfo : pbinfo.Info {
1421+ Type : make (map [string ]pbinfo.ProtoType ),
1422+ ParentFile : make (map [protoreflect.ProtoMessage ]* descriptorpb.FileDescriptorProto ),
1423+ },
1424+ }
1425+
1426+ m := & descriptorpb.MethodDescriptorProto {
1427+ Name : proto .String ("TestMethod" ),
1428+ InputType : proto .String (".InputType" ),
1429+ Options : & descriptorpb.MethodOptions {},
1430+ }
1431+
1432+ // Setup InputType with fields used in headers
1433+ inputType := & descriptorpb.DescriptorProto {
1434+ Name : proto .String ("InputType" ),
1435+ Field : []* descriptorpb.FieldDescriptorProto {
1436+ {Name : proto .String ("field1" ), Type : typePtr (descriptorpb .FieldDescriptorProto_TYPE_STRING )},
1437+ {Name : proto .String ("field2" ), Type : typePtr (descriptorpb .FieldDescriptorProto_TYPE_STRING )},
1438+ {Name : proto .String ("field3" ), Type : typePtr (descriptorpb .FieldDescriptorProto_TYPE_STRING )},
1439+ },
1440+ }
1441+ g .descInfo .Type [".InputType" ] = inputType
1442+
1443+ // The input headers list defines the order:
1444+ // 1. header_a (from field1)
1445+ // 2. header_b (from field2)
1446+ // 3. header_a (from field3) -- Duplicate key
1447+ headers := [][]string {
1448+ {"regex1" , "field1" , "header_a" },
1449+ {"regex2" , "field2" , "header_b" },
1450+ {"regex3" , "field3" , "header_a" },
1451+ }
1452+
1453+ tests := []struct {
1454+ pkgName string
1455+ wantOrdered bool
1456+ }{
1457+ {"google.firestore.v1" , true },
1458+ {"google.firestore.admin.v1" , true },
1459+ {"google.cloud.storage.v1" , false },
1460+ {"other.package" , false },
1461+ }
1462+
1463+ for _ , tc := range tests {
1464+ t .Run (tc .pkgName , func (t * testing.T ) {
1465+ g .reset ()
1466+
1467+ file := & descriptorpb.FileDescriptorProto {
1468+ Package : proto .String (tc .pkgName ),
1469+ }
1470+ g .descInfo .ParentFile [m ] = file
1471+ g .descInfo .ParentFile [inputType ] = file
1472+
1473+ err := g .insertDynamicRequestHeaders (m , headers )
1474+ if err != nil {
1475+ t .Fatalf ("insertDynamicRequestHeaders failed: %v" , err )
1476+ }
1477+
1478+ got := g .pt .String ()
1479+
1480+ if tc .wantOrdered {
1481+ // Verify strict order of checks in the generated code.
1482+ // We expect code blocks for header_a, then header_b, then header_a again.
1483+
1484+ idxA1 := strings .Index (got , `if headerValue, ok := routingHeadersMap["header_a"]; ok {` )
1485+ if idxA1 == - 1 {
1486+ t .Errorf ("expected first check for header_a" )
1487+ }
1488+
1489+ // Look for header_b check AFTER header_a check
1490+ idxB := strings .Index (got [idxA1 + 1 :], `if headerValue, ok := routingHeadersMap["header_b"]; ok {` )
1491+ if idxB == - 1 {
1492+ t .Errorf ("expected check for header_b after first header_a" )
1493+ }
1494+ idxB += idxA1 + 1 // Adjust index to be absolute
1495+
1496+ // Look for second header_a check AFTER header_b check
1497+ idxA2 := strings .Index (got [idxB + 1 :], `if headerValue, ok := routingHeadersMap["header_a"]; ok {` )
1498+ if idxA2 == - 1 {
1499+ t .Errorf ("expected second check for header_a after header_b" )
1500+ }
1501+
1502+ // Verify delete is called
1503+ if ! strings .Contains (got , `delete(routingHeadersMap, "header_a")` ) {
1504+ t .Errorf ("expected delete map key for %s" , tc .pkgName )
1505+ }
1506+
1507+ // Verify absence of range loop
1508+ if strings .Contains (got , `range routingHeadersMap` ) {
1509+ t .Errorf ("expected NO range map iteration for %s" , tc .pkgName )
1510+ }
1511+ } else {
1512+ // Check for unordered iteration pattern: range loop
1513+ if ! strings .Contains (got , `for headerName, headerValue := range routingHeadersMap {` ) {
1514+ t .Errorf ("expected range map iteration for %s, got:\n %s" , tc .pkgName , got )
1515+ }
1516+ }
1517+ })
1518+ }
1519+ }
1520+
1521+ func typePtr (t descriptorpb.FieldDescriptorProto_Type ) * descriptorpb.FieldDescriptorProto_Type {
1522+ return & t
1523+ }
1524+
14171525func setHTTPOption (o * descriptorpb.MethodOptions , pattern string ) {
14181526 proto .SetExtension (o , annotations .E_Http , & annotations.HttpRule {
14191527 Pattern : & annotations.HttpRule_Get {
0 commit comments