Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
174 changes: 135 additions & 39 deletions router-tests/header_propagation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,13 +303,13 @@ func TestHeaderPropagation(t *testing.T) {
})
})

t.Run("MostRestrictiveCacheControl", func(t *testing.T) {
t.Run("Cache Control Propagation", func(t *testing.T) {
// Global test: All subgraphs' responses are considered and most restrictive cache directive wins
t.Run("global most restrictive cache control", func(t *testing.T) {
t.Run("enable global cache control", func(t *testing.T) {
t.Parallel()
testenv.Run(t, &testenv.Config{
RouterOptions: global(config.ResponseHeaderRuleAlgorithmMostRestrictiveCacheControl, "", ""),
Subgraphs: cacheOptions("max-age=120", "max-age=60"),
CacheControlPolicy: config.CacheControlPolicy{Enabled: true},
Subgraphs: cacheOptions("max-age=120", "max-age=60"),
}, func(t *testing.T, xEnv *testenv.Environment) {
res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{
Query: queryEmployeeWithHobby,
Expand All @@ -321,11 +321,16 @@ func TestHeaderPropagation(t *testing.T) {
})

// Local test: Cache control rules are applied per subgraph (employees and hobbies)
t.Run("local most restrictive cache control", func(t *testing.T) {
t.Run("only enable cache control for subgraphs", func(t *testing.T) {
t.Parallel()
testenv.Run(t, &testenv.Config{
RouterOptions: local(config.ResponseHeaderRuleAlgorithmMostRestrictiveCacheControl, "", "", ""),
Subgraphs: cacheOptions("max-age=120", "max-age=60"),
Subgraphs: cacheOptions("max-age=120", "max-age=60"),
CacheControlPolicy: config.CacheControlPolicy{
Subgraphs: []config.SubgraphCacheControlRule{
{Name: "employees"},
{Name: "hobbies"},
},
},
}, func(t *testing.T, xEnv *testenv.Environment) {
res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{
Query: queryEmployeeWithHobby,
Expand All @@ -337,11 +342,15 @@ func TestHeaderPropagation(t *testing.T) {
})

// Partial test: Only one subgraph's response is considered (e.g., employees)
t.Run("partial most restrictive cache control", func(t *testing.T) {
t.Run("only enable cache control for one subgraph", func(t *testing.T) {
t.Parallel()
testenv.Run(t, &testenv.Config{
RouterOptions: partial(config.ResponseHeaderRuleAlgorithmMostRestrictiveCacheControl, "", ""),
Subgraphs: cacheOptions("max-age=120", "max-age=60"),
CacheControlPolicy: config.CacheControlPolicy{
Subgraphs: []config.SubgraphCacheControlRule{
{Name: "employees"},
},
},
Subgraphs: cacheOptions("max-age=120", "max-age=60"),
}, func(t *testing.T, xEnv *testenv.Environment) {
res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{
Query: queryEmployeeWithHobby,
Expand All @@ -353,11 +362,11 @@ func TestHeaderPropagation(t *testing.T) {
})

// Test case for no-store being the most restrictive
t.Run("global no-store wins", func(t *testing.T) {
t.Run("global default value of no-store wins", func(t *testing.T) {
t.Parallel()
testenv.Run(t, &testenv.Config{
RouterOptions: global(config.ResponseHeaderRuleAlgorithmMostRestrictiveCacheControl, "", ""),
Subgraphs: cacheOptions("no-store", "max-age=300"),
CacheControlPolicy: config.CacheControlPolicy{Enabled: true},
Subgraphs: cacheOptions("no-store", "max-age=300"),
}, func(t *testing.T, xEnv *testenv.Environment) {
res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{
Query: queryEmployeeWithHobby,
Expand All @@ -369,11 +378,11 @@ func TestHeaderPropagation(t *testing.T) {
})

// Test case for no-cache being more restrictive than max-age
t.Run("global no-cache wins", func(t *testing.T) {
t.Run("global default value of no-cache wins", func(t *testing.T) {
t.Parallel()
testenv.Run(t, &testenv.Config{
RouterOptions: global(config.ResponseHeaderRuleAlgorithmMostRestrictiveCacheControl, "", ""),
Subgraphs: cacheOptions("no-cache", "max-age=300"),
CacheControlPolicy: config.CacheControlPolicy{Enabled: true},
Subgraphs: cacheOptions("no-cache", "max-age=300"),
}, func(t *testing.T, xEnv *testenv.Environment) {
res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{
Query: queryEmployeeWithHobby,
Expand All @@ -384,11 +393,11 @@ func TestHeaderPropagation(t *testing.T) {
})
})

t.Run("global no-cache wins against no value", func(t *testing.T) {
t.Run("no-cache wins against no value", func(t *testing.T) {
t.Parallel()
testenv.Run(t, &testenv.Config{
RouterOptions: global(config.ResponseHeaderRuleAlgorithmMostRestrictiveCacheControl, "", ""),
Subgraphs: cacheOptions("no-cache", ""),
CacheControlPolicy: config.CacheControlPolicy{Enabled: true},
Subgraphs: cacheOptions("no-cache", ""),
}, func(t *testing.T, xEnv *testenv.Environment) {
res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{
Query: queryEmployeeWithHobby,
Expand All @@ -400,11 +409,11 @@ func TestHeaderPropagation(t *testing.T) {
})

// Test case for max-age: shortest max-age wins
t.Run("global shortest max-age wins", func(t *testing.T) {
t.Run("shortest max-age wins", func(t *testing.T) {
t.Parallel()
testenv.Run(t, &testenv.Config{
RouterOptions: global(config.ResponseHeaderRuleAlgorithmMostRestrictiveCacheControl, "", ""),
Subgraphs: cacheOptions("max-age=600", "max-age=300"),
CacheControlPolicy: config.CacheControlPolicy{Enabled: true},
Subgraphs: cacheOptions("max-age=600", "max-age=300"),
}, func(t *testing.T, xEnv *testenv.Environment) {
res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{
Query: queryEmployeeWithHobby,
Expand All @@ -416,11 +425,11 @@ func TestHeaderPropagation(t *testing.T) {
})

// Test case for Expires header: earliest expiration wins
t.Run("global earliest Expires wins", func(t *testing.T) {
t.Run("earliest Expires wins", func(t *testing.T) {
t.Parallel()
testenv.Run(t, &testenv.Config{
RouterOptions: global(config.ResponseHeaderRuleAlgorithmMostRestrictiveCacheControl, "", ""),
Subgraphs: subgraphsWithExpiresHeader,
CacheControlPolicy: config.CacheControlPolicy{Enabled: true},
Subgraphs: subgraphsWithExpiresHeader,
}, func(t *testing.T, xEnv *testenv.Environment) {
res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{
Query: queryEmployeeWithHobby,
Expand All @@ -444,8 +453,11 @@ func TestHeaderPropagation(t *testing.T) {
t.Run("global default age sets for all requests", func(t *testing.T) {
t.Parallel()
testenv.Run(t, &testenv.Config{
RouterOptions: global(config.ResponseHeaderRuleAlgorithmMostRestrictiveCacheControl, "", "max-age=300"),
Subgraphs: cacheOptions("", "max-age=600"),
CacheControlPolicy: config.CacheControlPolicy{
Enabled: true,
Value: "max-age=300",
},
Subgraphs: cacheOptions("", "max-age=600"),
}, func(t *testing.T, xEnv *testenv.Environment) {
res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{
Query: queryEmployeeWithHobby,
Expand All @@ -459,8 +471,11 @@ func TestHeaderPropagation(t *testing.T) {
t.Run("global no-cache sets for all requests", func(t *testing.T) {
t.Parallel()
testenv.Run(t, &testenv.Config{
RouterOptions: global(config.ResponseHeaderRuleAlgorithmMostRestrictiveCacheControl, "", "no-cache"),
Subgraphs: cacheOptions("", "max-age=600"),
CacheControlPolicy: config.CacheControlPolicy{
Enabled: true,
Value: "no-cache",
},
Subgraphs: cacheOptions("", "max-age=600"),
}, func(t *testing.T, xEnv *testenv.Environment) {
res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{
Query: queryEmployeeWithHobby,
Expand All @@ -474,8 +489,11 @@ func TestHeaderPropagation(t *testing.T) {
t.Run("global default age sets for all requests", func(t *testing.T) {
t.Parallel()
testenv.Run(t, &testenv.Config{
RouterOptions: global(config.ResponseHeaderRuleAlgorithmMostRestrictiveCacheControl, "", "no-cache"),
Subgraphs: cacheOptions("max-age=60", "max-age=300"),
CacheControlPolicy: config.CacheControlPolicy{
Enabled: true,
Value: "no-cache",
},
Subgraphs: cacheOptions("max-age=60", "max-age=300"),
}, func(t *testing.T, xEnv *testenv.Environment) {
res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{
Query: queryEmployeeWithHobby,
Expand All @@ -489,8 +507,11 @@ func TestHeaderPropagation(t *testing.T) {
t.Run("allows subgraph to override default", func(t *testing.T) {
t.Parallel()
testenv.Run(t, &testenv.Config{
RouterOptions: global(config.ResponseHeaderRuleAlgorithmMostRestrictiveCacheControl, "", "max-age=300"),
Subgraphs: cacheOptions("max-age=60", "max-age=180"),
CacheControlPolicy: config.CacheControlPolicy{
Enabled: true,
Value: "max-age=300",
},
Subgraphs: cacheOptions("max-age=60", "max-age=180"),
}, func(t *testing.T, xEnv *testenv.Environment) {
res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{
Query: queryEmployeeWithHobby,
Expand All @@ -504,8 +525,13 @@ func TestHeaderPropagation(t *testing.T) {
t.Run("partial default age sets for requests with information", func(t *testing.T) {
t.Parallel()
testenv.Run(t, &testenv.Config{
RouterOptions: local(config.ResponseHeaderRuleAlgorithmMostRestrictiveCacheControl, "", "", "max-age=300"),
Subgraphs: cacheOptions("", ""),
CacheControlPolicy: config.CacheControlPolicy{
Subgraphs: []config.SubgraphCacheControlRule{
{Name: "employees"},
{Name: "hobbies", Value: "max-age=300"},
},
},
Subgraphs: cacheOptions("", ""),
}, func(t *testing.T, xEnv *testenv.Environment) {
res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{
Query: queryEmployeeWithHobby,
Expand All @@ -519,8 +545,13 @@ func TestHeaderPropagation(t *testing.T) {
t.Run("partial default age doesn't set for unassociated requests", func(t *testing.T) {
t.Parallel()
testenv.Run(t, &testenv.Config{
RouterOptions: local(config.ResponseHeaderRuleAlgorithmMostRestrictiveCacheControl, "", "", "max-age=300"),
Subgraphs: cacheOptions("", ""),
CacheControlPolicy: config.CacheControlPolicy{
Subgraphs: []config.SubgraphCacheControlRule{
{Name: "employees"},
{Name: "hobbies", Value: "max-age=300"},
},
},
Subgraphs: cacheOptions("", ""),
}, func(t *testing.T, xEnv *testenv.Environment) {
res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{
Query: queryEmployeeWithNoHobby,
Expand All @@ -534,8 +565,11 @@ func TestHeaderPropagation(t *testing.T) {
t.Run("no-cache is set for all mutations", func(t *testing.T) {
t.Parallel()
testenv.Run(t, &testenv.Config{
RouterOptions: global(config.ResponseHeaderRuleAlgorithmMostRestrictiveCacheControl, "", "max-age=300"),
Subgraphs: cacheOptions("", ""),
CacheControlPolicy: config.CacheControlPolicy{
Enabled: true,
Value: "max-age=300",
},
Subgraphs: cacheOptions("", ""),
}, func(t *testing.T, xEnv *testenv.Environment) {
res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{
Query: `mutation { updateEmployeeTag(id: 1, tag: "test") { id tag } }`,
Expand All @@ -547,5 +581,67 @@ func TestHeaderPropagation(t *testing.T) {
})
})
})

t.Run("set operation can override cache control policies", func(t *testing.T) {
t.Run("global set operation", func(t *testing.T) {
t.Parallel()
testenv.Run(t, &testenv.Config{
CacheControlPolicy: config.CacheControlPolicy{
Enabled: true,
Value: "max-age=300",
},
Subgraphs: cacheOptions("max-age=180", "max-age=250"),
RouterOptions: []core.Option{core.WithHeaderRules(config.HeaderRules{
All: &config.GlobalHeaderRule{
Response: []*config.ResponseHeaderRule{
&config.ResponseHeaderRule{
Operation: config.HeaderRuleOperationSet,
Name: "Cache-Control",
Value: "my-fake-value",
},
},
},
})},
}, func(t *testing.T, xEnv *testenv.Environment) {
res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{
Query: queryEmployeeWithHobby,
})
cc := res.Response.Header.Get("Cache-Control")
require.Equal(t, "my-fake-value", cc)
require.Equal(t, `{"data":{"employee":{"id":1,"hobbies":[{},{"name":"Counter Strike"},{},{},{}]}}}`, res.Body)
})
})

t.Run("local subgraph set operation", func(t *testing.T) {
t.Parallel()
testenv.Run(t, &testenv.Config{
CacheControlPolicy: config.CacheControlPolicy{
Enabled: true,
Value: "max-age=300",
},
Subgraphs: cacheOptions("max-age=180", "max-age=250"),
RouterOptions: []core.Option{core.WithHeaderRules(config.HeaderRules{
Subgraphs: map[string]*config.GlobalHeaderRule{
"employees": &config.GlobalHeaderRule{
Response: []*config.ResponseHeaderRule{
&config.ResponseHeaderRule{
Operation: config.HeaderRuleOperationSet,
Name: "Cache-Control",
Value: "my-fake-value",
},
},
},
},
})},
}, func(t *testing.T, xEnv *testenv.Environment) {
res := xEnv.MakeGraphQLRequestOK(testenv.GraphQLRequest{
Query: queryEmployeeWithHobby,
})
cc := res.Response.Header.Get("Cache-Control")
require.Equal(t, "my-fake-value", cc)
require.Equal(t, `{"data":{"employee":{"id":1,"hobbies":[{},{"name":"Counter Strike"},{},{},{}]}}}`, res.Body)
})
})
})
})
}
3 changes: 3 additions & 0 deletions router-tests/testenv/testenv.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ type Config struct {
ShutdownDelay time.Duration
NoRetryClient bool
PropagationConfig config.PropagationConfig
CacheControlPolicy config.CacheControlPolicy
LogObservation LogObservationConfig
}

Expand Down Expand Up @@ -579,6 +580,7 @@ func configureRouter(listenerAddr string, testConfig *Config, routerConfig *node
RewritePaths: true,
AllowedExtensionFields: []string{"code"},
},
CacheControl: testConfig.CacheControlPolicy,
}

if testConfig.ModifyCDNConfig != nil {
Expand Down Expand Up @@ -650,6 +652,7 @@ func configureRouter(listenerAddr string, testConfig *Config, routerConfig *node
core.WithPlayground(true),
core.WithEngineExecutionConfig(engineExecutionConfig),
core.WithSecurityConfig(cfg.SecurityConfiguration),
core.WithCacheControlPolicy(cfg.CacheControl),
core.WithCDN(cfg.CDN),
core.WithListenerAddr(listenerAddr),
core.WithSubgraphErrorPropagation(cfg.SubgraphErrorPropagation),
Expand Down
1 change: 1 addition & 0 deletions router/cmd/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ func NewRouter(params Params, additionalOptions ...core.Option) (*core.Router, e
core.WithTracing(core.TraceConfigFromTelemetry(&cfg.Telemetry)),
core.WithMetrics(core.MetricConfigFromTelemetry(&cfg.Telemetry)),
core.WithEngineExecutionConfig(cfg.EngineExecutionConfiguration),
core.WithCacheControlPolicy(cfg.CacheControl),
core.WithSecurityConfig(cfg.SecurityConfiguration),
core.WithAuthorizationConfig(&cfg.Authorization),
core.WithWebSocketConfiguration(&cfg.WebSocket),
Expand Down
Loading