@@ -1707,3 +1707,143 @@ func TestSetGRPCHeaderRouteTwoDistinctNamesAppendsBoth(t *testing.T) {
17071707 assert .Equal (t , "header-route-one" , string (* rpcPluginImp .UpdatedGRPCRouteMock .Spec .Rules [1 ].Name ))
17081708 assert .Equal (t , "header-route-two" , string (* rpcPluginImp .UpdatedGRPCRouteMock .Spec .Rules [2 ].Name ))
17091709}
1710+
1711+ // TestGetRouteRuleOnlyReturnsRuleWithAllBackends verifies that getRouteRule only returns
1712+ // a route rule when ALL requested backends are present. This is a regression test for the
1713+ // bug where getRouteRule would return the first rule with any matching backend, even if
1714+ // not all backends were found.
1715+ //
1716+ // Bug scenario: When searching for a rule with both "canary" and "stable" backends,
1717+ // getRouteRule would incorrectly return a header route (which only has "canary" backend)
1718+ // if it appeared first in the rules list.
1719+ func TestGetRouteRuleOnlyReturnsRuleWithAllBackends (t * testing.T ) {
1720+ // Create a route with two rules:
1721+ // 1. Header route with only canary backend (managed route)
1722+ // 2. Main route with both canary and stable backends
1723+ canaryName := gatewayv1 .SectionName ("header-route" )
1724+ weight30 := int32 (30 )
1725+ weight70 := int32 (70 )
1726+
1727+ httpRoute := & gatewayv1.HTTPRoute {
1728+ ObjectMeta : metav1.ObjectMeta {
1729+ Name : "test-route" ,
1730+ Namespace : "default" ,
1731+ },
1732+ Spec : gatewayv1.HTTPRouteSpec {
1733+ Rules : []gatewayv1.HTTPRouteRule {
1734+ // Header route: only canary backend (this should NOT be returned)
1735+ {
1736+ Name : & canaryName ,
1737+ Matches : []gatewayv1.HTTPRouteMatch {
1738+ {
1739+ Headers : []gatewayv1.HTTPHeaderMatch {
1740+ {
1741+ Name : "X-Canary" ,
1742+ Value : "true" ,
1743+ },
1744+ },
1745+ },
1746+ },
1747+ BackendRefs : []gatewayv1.HTTPBackendRef {
1748+ {
1749+ BackendRef : gatewayv1.BackendRef {
1750+ BackendObjectReference : gatewayv1.BackendObjectReference {
1751+ Name : "argo-rollouts-canary-service" ,
1752+ },
1753+ },
1754+ },
1755+ },
1756+ },
1757+ // Main route: both canary and stable backends (this SHOULD be returned)
1758+ {
1759+ BackendRefs : []gatewayv1.HTTPBackendRef {
1760+ {
1761+ BackendRef : gatewayv1.BackendRef {
1762+ BackendObjectReference : gatewayv1.BackendObjectReference {
1763+ Name : "argo-rollouts-stable-service" ,
1764+ },
1765+ Weight : & weight70 ,
1766+ },
1767+ },
1768+ {
1769+ BackendRef : gatewayv1.BackendRef {
1770+ BackendObjectReference : gatewayv1.BackendObjectReference {
1771+ Name : "argo-rollouts-canary-service" ,
1772+ },
1773+ Weight : & weight30 ,
1774+ },
1775+ },
1776+ },
1777+ },
1778+ },
1779+ },
1780+ }
1781+
1782+ httpRouteRuleList := HTTPRouteRuleList (httpRoute .Spec .Rules )
1783+
1784+ // Test: Search for rule with both backends
1785+ backendRefNameList := []string {"argo-rollouts-canary-service" , "argo-rollouts-stable-service" }
1786+ httpRouteRule , err := getRouteRule (httpRouteRuleList , backendRefNameList ... )
1787+
1788+ // Assert: Should find the main route (second rule), not the header route (first rule)
1789+ assert .NoError (t , err , "Should find a route with both backends" )
1790+ assert .NotNil (t , httpRouteRule , "Route rule should not be nil" )
1791+ assert .Equal (t , 2 , len (httpRouteRule .BackendRefs ), "Should return rule with 2 backends" )
1792+ assert .Nil (t , httpRouteRule .Name , "Main route should not have a name (header routes have names)" )
1793+
1794+ // Verify it's the correct rule by checking backend weights
1795+ var foundStable , foundCanary bool
1796+ for _ , ref := range httpRouteRule .BackendRefs {
1797+ if ref .Name == "argo-rollouts-stable-service" {
1798+ foundStable = true
1799+ assert .Equal (t , int32 (70 ), * ref .Weight )
1800+ }
1801+ if ref .Name == "argo-rollouts-canary-service" {
1802+ foundCanary = true
1803+ assert .Equal (t , int32 (30 ), * ref .Weight )
1804+ }
1805+ }
1806+ assert .True (t , foundStable , "Should have stable backend" )
1807+ assert .True (t , foundCanary , "Should have canary backend" )
1808+ }
1809+
1810+ // TestGetRouteRuleReturnsErrorWhenBackendNotFound verifies that getRouteRule returns
1811+ // an error when no rule contains all requested backends.
1812+ func TestGetRouteRuleReturnsErrorWhenBackendNotFound (t * testing.T ) {
1813+ // Create a route with only header routes (single backend each)
1814+ canaryName := gatewayv1 .SectionName ("header-route" )
1815+
1816+ httpRoute := & gatewayv1.HTTPRoute {
1817+ ObjectMeta : metav1.ObjectMeta {
1818+ Name : "test-route" ,
1819+ Namespace : "default" ,
1820+ },
1821+ Spec : gatewayv1.HTTPRouteSpec {
1822+ Rules : []gatewayv1.HTTPRouteRule {
1823+ {
1824+ Name : & canaryName ,
1825+ BackendRefs : []gatewayv1.HTTPBackendRef {
1826+ {
1827+ BackendRef : gatewayv1.BackendRef {
1828+ BackendObjectReference : gatewayv1.BackendObjectReference {
1829+ Name : "argo-rollouts-canary-service" ,
1830+ },
1831+ },
1832+ },
1833+ },
1834+ },
1835+ },
1836+ },
1837+ }
1838+
1839+ httpRouteRuleList := HTTPRouteRuleList (httpRoute .Spec .Rules )
1840+
1841+ // Test: Search for rule with both backends (only canary exists)
1842+ backendRefNameList := []string {"argo-rollouts-canary-service" , "argo-rollouts-stable-service" }
1843+ httpRouteRule , err := getRouteRule (httpRouteRuleList , backendRefNameList ... )
1844+
1845+ // Assert: Should return error because no rule has both backends
1846+ assert .Error (t , err , "Should return error when backend not found" )
1847+ assert .Nil (t , httpRouteRule , "Route rule should be nil on error" )
1848+ assert .Equal (t , BackendRefWasNotFoundInHTTPRouteError , err .Error ())
1849+ }
0 commit comments