-
Notifications
You must be signed in to change notification settings - Fork 234
feat: add Variables to OperationContext #2045
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
alepane21
merged 3 commits into
main
from
ale/eng-7598-add-variables-to-requestcontextoperation
Jul 15, 2025
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
84 changes: 84 additions & 0 deletions
84
router-tests/modules/verify-operation-context-values/module.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| package verify_operation_context_values | ||
|
|
||
| import ( | ||
| "net/http" | ||
|
|
||
| "github.com/wundergraph/astjson" | ||
| "github.com/wundergraph/cosmo/router/core" | ||
| "go.uber.org/zap" | ||
| ) | ||
|
|
||
| const myModuleID = "verifyOperationContextValues" | ||
|
|
||
| // CapturedOperationValues holds the captured values from operation context | ||
| type CapturedOperationValues struct { | ||
| Name string | ||
| Type string | ||
| Hash uint64 | ||
| Content string | ||
| Variables *astjson.Value | ||
| // Store the raw variables as string for easier testing | ||
| VariablesJSON string | ||
| ClientInfo core.ClientInfo | ||
| } | ||
|
|
||
| // VerifyOperationContextValuesModule captures operation context values for verification | ||
| type VerifyOperationContextValuesModule struct { | ||
| ResultsChan chan CapturedOperationValues | ||
| Logger *zap.Logger | ||
| } | ||
|
|
||
| func (m *VerifyOperationContextValuesModule) Provision(ctx *core.ModuleContext) error { | ||
| m.Logger = ctx.Logger | ||
| if m.ResultsChan == nil { | ||
| m.ResultsChan = make(chan CapturedOperationValues, 1) | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| func (m *VerifyOperationContextValuesModule) Middleware(ctx core.RequestContext, next http.Handler) { | ||
| operation := ctx.Operation() | ||
|
|
||
| // Capture all the operation context values | ||
| captured := CapturedOperationValues{ | ||
| Name: operation.Name(), | ||
| Type: operation.Type(), | ||
| Hash: operation.Hash(), | ||
| Content: operation.Content(), | ||
| Variables: operation.Variables(), | ||
| ClientInfo: operation.ClientInfo(), | ||
| } | ||
|
|
||
| // Convert variables to JSON string for easier testing | ||
| captured.VariablesJSON = "{}" | ||
| if captured.Variables != nil { | ||
| variablesBytes := captured.Variables.MarshalTo(nil) | ||
| captured.VariablesJSON = string(variablesBytes) | ||
| } | ||
| // Send the captured values to the test | ||
| select { | ||
| case m.ResultsChan <- captured: | ||
| default: | ||
| // Channel is full, skip | ||
| } | ||
|
|
||
| // Call the next handler in the chain | ||
| next.ServeHTTP(ctx.ResponseWriter(), ctx.Request()) | ||
| } | ||
|
|
||
| func (m *VerifyOperationContextValuesModule) Module() core.ModuleInfo { | ||
| return core.ModuleInfo{ | ||
| ID: myModuleID, | ||
| Priority: 1, | ||
| New: func() core.Module { | ||
| return &VerifyOperationContextValuesModule{ | ||
| ResultsChan: make(chan CapturedOperationValues, 1), | ||
| } | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| // Interface guard | ||
| var ( | ||
| _ core.RouterMiddlewareHandler = (*VerifyOperationContextValuesModule)(nil) | ||
| ) | ||
195 changes: 195 additions & 0 deletions
195
router-tests/modules/verify_operation_context_values_test.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,195 @@ | ||
| package module_test | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "testing" | ||
| "time" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| verifyModule "github.com/wundergraph/cosmo/router-tests/modules/verify-operation-context-values" | ||
| "github.com/wundergraph/cosmo/router-tests/testenv" | ||
| "github.com/wundergraph/cosmo/router/core" | ||
| "github.com/wundergraph/cosmo/router/pkg/config" | ||
| "go.uber.org/zap/zapcore" | ||
| ) | ||
|
|
||
| func TestVerifyOperationContextValues(t *testing.T) { | ||
| t.Run("verifies all operation context values are set correctly", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| resultsChan := make(chan verifyModule.CapturedOperationValues, 1) | ||
|
|
||
| cfg := config.Config{ | ||
| Graph: config.Graph{}, | ||
| Modules: map[string]any{ | ||
| "verifyOperationContextValues": verifyModule.VerifyOperationContextValuesModule{ | ||
| ResultsChan: resultsChan, | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| testenv.Run(t, &testenv.Config{ | ||
| RouterOptions: []core.Option{ | ||
| core.WithModulesConfig(cfg.Modules), | ||
| core.WithCustomModules(&verifyModule.VerifyOperationContextValuesModule{}), | ||
| }, | ||
| LogObservation: testenv.LogObservationConfig{ | ||
| Enabled: true, | ||
| LogLevel: zapcore.InfoLevel, | ||
| }, | ||
| }, func(t *testing.T, xEnv *testenv.Environment) { | ||
| // Send a GraphQL query with variables that are actually used | ||
| res, err := xEnv.MakeGraphQLRequest(testenv.GraphQLRequest{ | ||
| Query: `query GetEmployee($empId: Int!) { | ||
| employee(id: $empId) { | ||
| id | ||
| details { | ||
| forename | ||
| surname | ||
| } | ||
| tag | ||
| } | ||
| }`, | ||
| Variables: json.RawMessage(`{"empId": 1}`), | ||
| OperationName: json.RawMessage(`"GetEmployee"`), | ||
| }) | ||
| require.NoError(t, err) | ||
| assert.Equal(t, 200, res.Response.StatusCode) | ||
|
|
||
| // Wait for the module to capture the operation context values | ||
| testenv.AwaitChannelWithT(t, 10*time.Second, resultsChan, func(t *testing.T, captured verifyModule.CapturedOperationValues) { | ||
| // Verify operation name | ||
| assert.Equal(t, "GetEmployee", captured.Name, "Operation name should be set correctly") | ||
|
|
||
| // Verify operation type | ||
| assert.Equal(t, "query", captured.Type, "Operation type should be 'query'") | ||
|
|
||
| // Verify operation hash is set (non-zero) | ||
| assert.NotZero(t, captured.Hash, "Operation hash should be set") | ||
|
|
||
| // Verify operation content is set and contains the normalized query | ||
| assert.Equal(t, captured.Content, "query GetEmployee($a: Int!){employee(id: $a){id details {forename surname} tag}}", "Operation content should be set") | ||
|
|
||
| // Verify Variables() method returns the correct variables | ||
| assert.NotNil(t, captured.Variables, "Variables should not be nil") | ||
|
|
||
| // Verify the variables JSON contains the expected values | ||
| assert.JSONEq(t, `{"empId": 1}`, captured.VariablesJSON, "Variables JSON should match the sent variables") | ||
|
|
||
| // Verify we can access individual variables | ||
| empIdVar := captured.Variables.Get("empId") | ||
| assert.NotNil(t, empIdVar, "Should be able to access 'empId' variable") | ||
| assert.Equal(t, 1, empIdVar.GetInt(), "empId variable should be 1") | ||
|
|
||
| // Verify client info is populated (at least the basic structure) | ||
| assert.NotNil(t, captured.ClientInfo, "Client info should be set") | ||
| }) | ||
| }) | ||
| }) | ||
|
|
||
| t.Run("verifies context values with empty variables", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| resultsChan := make(chan verifyModule.CapturedOperationValues, 1) | ||
|
|
||
| cfg := config.Config{ | ||
| Graph: config.Graph{}, | ||
| Modules: map[string]any{ | ||
| "verifyOperationContextValues": verifyModule.VerifyOperationContextValuesModule{ | ||
| ResultsChan: resultsChan, | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| testenv.Run(t, &testenv.Config{ | ||
| RouterOptions: []core.Option{ | ||
| core.WithModulesConfig(cfg.Modules), | ||
| core.WithCustomModules(&verifyModule.VerifyOperationContextValuesModule{}), | ||
| }, | ||
| }, func(t *testing.T, xEnv *testenv.Environment) { | ||
| // Send a simple query without variables | ||
| res, err := xEnv.MakeGraphQLRequest(testenv.GraphQLRequest{ | ||
| Query: `query SimpleQuery { employees { id } }`, | ||
| OperationName: json.RawMessage(`"SimpleQuery"`), | ||
| }) | ||
| require.NoError(t, err) | ||
| assert.Equal(t, 200, res.Response.StatusCode) | ||
|
|
||
| // Wait for the module to capture the operation context values | ||
| testenv.AwaitChannelWithT(t, 10*time.Second, resultsChan, func(t *testing.T, captured verifyModule.CapturedOperationValues) { | ||
| // Verify operation name | ||
| assert.Equal(t, "SimpleQuery", captured.Name, "Operation name should be set correctly") | ||
|
|
||
| // Verify operation type | ||
| assert.Equal(t, "query", captured.Type, "Operation type should be 'query'") | ||
|
|
||
| // Verify Variables() method works with empty variables | ||
| assert.NotNil(t, captured.Variables, "Variables should not be nil even when empty") | ||
|
|
||
| // Verify the variables JSON is an empty object | ||
| assert.JSONEq(t, `{}`, captured.VariablesJSON, "Variables JSON should be empty object when no variables provided") | ||
| }) | ||
| }) | ||
| }) | ||
|
|
||
| t.Run("verifies context values with mutation", func(t *testing.T) { | ||
| t.Parallel() | ||
|
|
||
| resultsChan := make(chan verifyModule.CapturedOperationValues, 1) | ||
|
|
||
| cfg := config.Config{ | ||
| Graph: config.Graph{}, | ||
| Modules: map[string]any{ | ||
| "verifyOperationContextValues": verifyModule.VerifyOperationContextValuesModule{ | ||
| ResultsChan: resultsChan, | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| testenv.Run(t, &testenv.Config{ | ||
| RouterOptions: []core.Option{ | ||
| core.WithModulesConfig(cfg.Modules), | ||
| core.WithCustomModules(&verifyModule.VerifyOperationContextValuesModule{}), | ||
| }, | ||
| }, func(t *testing.T, xEnv *testenv.Environment) { | ||
| // Send a mutation with variables | ||
| res, err := xEnv.MakeGraphQLRequest(testenv.GraphQLRequest{ | ||
| Query: `mutation UpdateEmployee($empId: Int!, $newTag: String!) { | ||
| updateEmployeeTag(id: $empId, tag: $newTag) { | ||
| id | ||
| tag | ||
| } | ||
| }`, | ||
| Variables: json.RawMessage(`{"empId": 1, "newTag": "Updated by test"}`), | ||
| OperationName: json.RawMessage(`"UpdateEmployee"`), | ||
| }) | ||
| require.NoError(t, err) | ||
| assert.Equal(t, 200, res.Response.StatusCode) | ||
|
|
||
| // Wait for the module to capture the operation context values | ||
| testenv.AwaitChannelWithT(t, 10*time.Second, resultsChan, func(t *testing.T, captured verifyModule.CapturedOperationValues) { | ||
| // Verify operation name | ||
| assert.Equal(t, "UpdateEmployee", captured.Name, "Operation name should be set correctly") | ||
|
|
||
| // Verify operation type is mutation | ||
| assert.Equal(t, "mutation", captured.Type, "Operation type should be 'mutation'") | ||
|
|
||
| // Verify Variables() method returns the correct mutation variables | ||
| assert.NotNil(t, captured.Variables, "Variables should not be nil") | ||
|
|
||
| // Verify the variables contain the mutation input | ||
| empIdVar := captured.Variables.Get("empId") | ||
| assert.NotNil(t, empIdVar, "Should be able to access 'empId' variable") | ||
| assert.Equal(t, 1, empIdVar.GetInt(), "empId variable should be 1") | ||
|
|
||
| newTagVar := captured.Variables.Get("newTag") | ||
| assert.NotNil(t, newTagVar, "Should be able to access 'newTag' variable") | ||
|
|
||
| // Verify string variable | ||
| newTagBytes := newTagVar.GetStringBytes() | ||
| assert.Equal(t, "Updated by test", string(newTagBytes), "newTag should be 'Updated by test'") | ||
| }) | ||
| }) | ||
| }) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.