Skip to main content
Glama
plugin_dispatcher_test.legacy16 kB
//go:build !pro package runner_test import ( "lunar/engine/config" lunarMessages "lunar/engine/messages" "lunar/engine/runner" "lunar/engine/services" sharedActions "lunar/shared-model/actions" sharedConfig "lunar/shared-model/config" "lunar/toolkit-core/clock" "lunar/toolkit-core/urltree" "testing" "github.com/goccy/go-json" "github.com/negasus/haproxy-spoe-go/action" "github.com/stretchr/testify/assert" ) var requestActiveRemediesAction = action.Action{ Name: "request_active_remedies", Scope: action.ScopeTransaction, Value: []byte("{}"), Type: action.TypeSetVar, } var responseActiveRemediesAction = action.Action{ Name: "response_active_remedies", Scope: action.ScopeTransaction, Value: []byte("{}"), Type: action.TypeSetVar, } func TestGivenOnRequestAndNoMatchingPoliciesASingleEmptyActionIsReturned( t *testing.T, ) { t.Parallel() clock := clock.NewMockClock() onRequest := lunarMessages.OnRequest{ ID: "1234-5678-9012-3456", SequenceID: "1234-5678-9012-3456", Method: "GET", Scheme: "http", URL: "twitter.com/post/1234", Path: "/post/1234", Query: "", Headers: map[string]string{"Host": "twitter.com"}, Body: "", Time: clock.Now(), } policyTree := fixedRemedyEndpointPolicyTree() globalPolicies := globalPolicies() accounts := accounts() mockWriter := newMockWriter() exporterConfig := sharedConfig.Exporters{} services, _ := services.Initialize( mockWriter, proxyTimeout, exporterConfig, ) diagnosisWorker := runner.NewDiagnosisWorker() policiesAccessor := config.SimplePolicyAccessor{ PoliciesData: &config.PoliciesData{ Config: sharedConfig.PoliciesConfig{ Global: *globalPolicies, Accounts: accounts, }, EndpointPolicyTree: *policyTree, }, } diagnosisWorker.Run( &policiesAccessor, &services.Diagnosis, &services.Exporters, ) actions, err := runner.DispatchOnRequest( onRequest, policyTree, &policiesAccessor.PoliciesData.Config, services, diagnosisWorker, ) assert.Nil(t, err) wantedActions := action.Actions{requestActiveRemediesAction} assert.Equal(t, wantedActions, actions) } func TestGivenOnRequestAndGlobalFixedResponseRemedyWithoutHeaderASingleEmptyActionIsReturned( t *testing.T, ) { t.Parallel() clock := clock.NewMockClock() onRequest := lunarMessages.OnRequest{ ID: "1234-5678-9012-3456", SequenceID: "1234-5678-9012-3456", Method: "GET", Scheme: "http", URL: "twitter.com/user/1234", Path: "/user/1234", Query: "", Headers: map[string]string{"Host": "twitter.com"}, Body: "", Time: clock.Now(), } policyTree := fixedRemedyEndpointPolicyTree() globalPolicies := globalPoliciesWithFixedResponseRemedy() accounts := accounts() mockWriter := newMockWriter() exporterConfig := sharedConfig.Exporters{} services, _ := services.Initialize( mockWriter, proxyTimeout, exporterConfig, ) diagnosisWorker := runner.NewDiagnosisWorker() policiesAccessor := config.SimplePolicyAccessor{ PoliciesData: &config.PoliciesData{ Config: sharedConfig.PoliciesConfig{ Global: *globalPolicies, Accounts: accounts, }, EndpointPolicyTree: *policyTree, }, } diagnosisWorker.Run( &policiesAccessor, &services.Diagnosis, &services.Exporters, ) actions, err := runner.DispatchOnRequest( onRequest, policyTree, &policiesAccessor.PoliciesData.Config, services, diagnosisWorker, ) assert.Nil(t, err) wantedActions := action.Actions{requestActiveRemediesAction} assert.Equal(t, wantedActions, actions) } func TestGivenOnRequestAndGlobalFixedResponseRemedyWithHeaderEarlyResponseActionsAreReturned( t *testing.T, ) { t.Parallel() clock := clock.NewMockClock() onRequest := lunarMessages.OnRequest{ ID: "1234-5678-9012-3456", SequenceID: "1234-5678-9012-3456", Method: "GET", Scheme: "http", URL: "twitter.com/user/1234", Path: "/user/1234", Query: "", Headers: map[string]string{ "host": "twitter.com", "early-response": "true", }, Body: "", Time: clock.Now(), } policyTree := fixedRemedyEndpointPolicyTree() globalPolicies := globalPoliciesWithFixedResponseRemedy() accounts := accounts() mockWriter := newMockWriter() exporterConfig := sharedConfig.Exporters{} services, _ := services.Initialize( mockWriter, proxyTimeout, exporterConfig, ) diagnosisWorker := runner.NewDiagnosisWorker() policiesAccessor := config.SimplePolicyAccessor{ PoliciesData: &config.PoliciesData{ Config: sharedConfig.PoliciesConfig{ Global: *globalPolicies, Accounts: accounts, }, EndpointPolicyTree: *policyTree, }, } diagnosisWorker.Run( &policiesAccessor, &services.Diagnosis, &services.Exporters, ) actions, err := runner.DispatchOnRequest( onRequest, policyTree, &policiesAccessor.PoliciesData.Config, services, diagnosisWorker, ) assert.Nil(t, err) wantActions := fixedEarlyResponseActions() assert.Equal(t, wantActions, actions) } func TestGivenOnRequestAndAMatchingFixedResponseRemedyWithHeaderEarlyResponseActionsAreReturned( t *testing.T, ) { t.Parallel() clock := clock.NewMockClock() onRequest := lunarMessages.OnRequest{ ID: "1234-5678-9012-3456", SequenceID: "1234-5678-9012-3456", Method: "GET", Scheme: "http", URL: "twitter.com/user/1234/messages", Path: "/user/1234/messages", Query: "", Headers: map[string]string{ "host": "twitter.com", "early-response": "true", }, Body: "", Time: clock.Now(), } policyTree := fixedRemedyEndpointPolicyTree() globalPolicies := globalPolicies() accounts := accounts() mockWriter := newMockWriter() exporterConfig := sharedConfig.Exporters{} services, _ := services.Initialize( mockWriter, proxyTimeout, exporterConfig, ) diagnosisWorker := runner.NewDiagnosisWorker() policiesAccessor := config.SimplePolicyAccessor{ PoliciesData: &config.PoliciesData{ Config: sharedConfig.PoliciesConfig{ Global: *globalPolicies, Accounts: accounts, }, EndpointPolicyTree: *policyTree, }, } diagnosisWorker.Run( &policiesAccessor, &services.Diagnosis, &services.Exporters, ) actions, err := runner.DispatchOnRequest( onRequest, policyTree, &policiesAccessor.PoliciesData.Config, services, diagnosisWorker, ) assert.Nil(t, err) wantActions := fixedEarlyResponseActions() assert.Equal(t, wantActions, actions) } func TestGivenOnResponseASingleNilErrorIsNil(t *testing.T) { t.Parallel() clock := clock.NewMockClock() onResponse := lunarMessages.OnResponse{ ID: "1234-5678-9012-3456", SequenceID: "3333-5678-9012-3456", Method: "GET", URL: "twitter.com/user/1234/messages", Headers: map[string]string{ "host": "twitter.com", "early-response": "true", }, Status: 200, Body: "", Time: clock.Now(), } policyTree := fixedRemedyEndpointPolicyTree() globalPolicies := globalPolicies() mockWriter := newMockWriter() exporterConfig := sharedConfig.Exporters{} services, _ := services.Initialize( mockWriter, proxyTimeout, exporterConfig, ) diagnosisWorker := runner.NewDiagnosisWorker() policiesAccessor := config.SimplePolicyAccessor{ PoliciesData: &config.PoliciesData{ Config: sharedConfig.PoliciesConfig{ Global: *globalPolicies, }, EndpointPolicyTree: *policyTree, }, } diagnosisWorker.Run( &policiesAccessor, &services.Diagnosis, &services.Exporters, ) actions, err := runner.DispatchOnResponse( onResponse, policyTree, globalPolicies, services, diagnosisWorker, ) assert.Nil(t, err) wantedActions := action.Actions{responseActiveRemediesAction} assert.Equal(t, wantedActions, actions) } func TestGivenMultipleGlobalRemediesWhenOnRequestIsCalledItReturnsOnlyEnabledRemedies( t *testing.T, ) { t.Skip("Skipping test as it is flaky") t.Parallel() clock := clock.NewMockClock() onRequest := lunarMessages.OnRequest{ ID: "1234-5678-9012-3456", SequenceID: "1234-5678-9012-3456", Method: "GET", Scheme: "http", URL: "no_specific_endpoint.com/foo/bar", Path: "/foo/bar", Query: "", Headers: map[string]string{ "host": "no_specific_endpoint.com", "early-response": "true", }, Body: "", Time: clock.Now(), } policyTree := fixedRemedyEndpointPolicyTree() globalPolicies := globalPoliciesWithMultipleRemedies() accounts := accounts() mockWriter := newMockWriter() exporterConfig := sharedConfig.Exporters{} services, _ := services.Initialize( mockWriter, proxyTimeout, exporterConfig, ) diagnosisWorker := runner.NewDiagnosisWorker() policiesAccessor := config.SimplePolicyAccessor{ PoliciesData: &config.PoliciesData{ Config: sharedConfig.PoliciesConfig{ Global: *globalPolicies, Accounts: accounts, }, EndpointPolicyTree: *policyTree, }, } diagnosisWorker.Run( &policiesAccessor, &services.Diagnosis, &services.Exporters, ) actions, err := runner.DispatchOnRequest( onRequest, policyTree, &policiesAccessor.PoliciesData.Config, services, diagnosisWorker, ) assert.Nil(t, err) assert.Equal(t, fixedEarlyResponseActions(), actions) } func fixedEarlyResponseActions() action.Actions { requestActiveRemedies := map[sharedConfig.RemedyType][]sharedActions.RemedyReqRunResult{ sharedConfig.RemedyFixedResponse: { sharedActions.ReqObtainedResponse, }, } requestActiveRemediesJSON, _ := json.Marshal(requestActiveRemedies) wantActions := action.Actions{ action.Action{ Name: "return_early_response", Scope: action.ScopeTransaction, Value: true, Type: action.TypeSetVar, }, action.Action{ Name: "is_internal", Scope: action.ScopeTransaction, Value: false, Type: action.TypeSetVar, }, action.Action{ Name: "status_code", Scope: action.ScopeTransaction, Value: 418, Type: action.TypeSetVar, }, action.Action{ Name: "response_body", Scope: action.ScopeTransaction, Value: []byte("{\"message\": \"GO Lunar\"}"), Type: action.TypeSetVar, }, action.Action{ Name: "response_headers", Scope: action.ScopeTransaction, Value: "powered-by:Lunar Interventions Inc.\n", Type: action.TypeSetVar, }, action.Action{ Name: "request_active_remedies", Scope: action.ScopeTransaction, Value: requestActiveRemediesJSON, Type: action.TypeSetVar, }, } return wantActions } func globalPoliciesWithFixedResponseRemedy() *sharedConfig.Global { globalPolicies := &sharedConfig.Global{ Remedies: []sharedConfig.Remedy{ { Name: "remedy1", Enabled: true, Config: sharedConfig.RemedyConfig{ FixedResponse: &sharedConfig.FixedResponseConfig{ StatusCode: 418, }, }, }, }, Diagnosis: []sharedConfig.Diagnosis{ { Name: "diagnosis1", Enabled: false, }, }, } return globalPolicies } func globalPoliciesWithMultipleRemedies() *sharedConfig.Global { globalPolicies := &sharedConfig.Global{ Remedies: []sharedConfig.Remedy{ { Name: "remedy1", Enabled: true, Config: sharedConfig.RemedyConfig{ FixedResponse: &sharedConfig.FixedResponseConfig{ StatusCode: 418, }, }, }, { Name: "remedy2", Enabled: false, Config: sharedConfig.RemedyConfig{ AccountOrchestration: &sharedConfig.AccountOrchestrationConfig{ RoundRobin: []sharedConfig.AccountID{ "account1", "account2", }, }, }, }, }, Diagnosis: []sharedConfig.Diagnosis{ { Name: "diagnosis1", Enabled: false, }, }, } return globalPolicies } func globalPolicies() *sharedConfig.Global { globalPolicies := &sharedConfig.Global{ Remedies: []sharedConfig.Remedy{}, Diagnosis: []sharedConfig.Diagnosis{}, } return globalPolicies } func accounts() map[sharedConfig.AccountID]sharedConfig.Account { return map[sharedConfig.AccountID]sharedConfig.Account{ "account1": { Tokens: []sharedConfig.Token{ { Header: &sharedConfig.Header{ Name: "Authorization", Value: "123", }, }, }, }, "account2": { Tokens: []sharedConfig.Token{ { Header: &sharedConfig.Header{ Name: "Authorization", Value: "456", }, }, }, }, } } func fixedRemedyEndpointPolicyTree() *urltree.EndpointTree[config.EndpointPolicy] { // policyTree, _ := config.BuildEndpointPolicyTree( []sharedConfig.EndpointConfig{ { Method: "GET", URL: "twitter.com/user/1234/messages", Remedies: []sharedConfig.Remedy{ { Name: "remedy1", Enabled: true, Config: sharedConfig.RemedyConfig{ FixedResponse: &sharedConfig.FixedResponseConfig{ StatusCode: 418, }, }, }, }, }, }, ) return policyTree } func diagnosisEndpointPolicyTree() *urltree.EndpointTree[config.EndpointPolicy] { policyTree, _ := config.BuildEndpointPolicyTree( []sharedConfig.EndpointConfig{ { Method: "GET", URL: "twitter.com/user/1234/messages", Diagnosis: []sharedConfig.Diagnosis{ { Name: "diagnosis1", Enabled: true, Config: sharedConfig.DiagnosisConfig{ HARExporter: &sharedConfig.HARExporterConfig{ TransactionMaxSize: 1000, Obfuscate: sharedConfig.Obfuscate{ Enabled: false, }, }, }, Export: "file", }, }, }, }, ) return policyTree } func fixedRemedyAndDiagnosisEndpointPolicyTree() *urltree.EndpointTree[config.EndpointPolicy] { policyTree, _ := config.BuildEndpointPolicyTree( []sharedConfig.EndpointConfig{ { Method: "GET", URL: "twitter.com/user/1234/messages", Remedies: []sharedConfig.Remedy{ { Name: "remedy1", Enabled: true, Config: sharedConfig.RemedyConfig{ FixedResponse: &sharedConfig.FixedResponseConfig{ StatusCode: 418, }, }, }, }, Diagnosis: []sharedConfig.Diagnosis{ { Name: "diagnosis1", Enabled: true, Config: sharedConfig.DiagnosisConfig{ HARExporter: &sharedConfig.HARExporterConfig{ TransactionMaxSize: 1000, Obfuscate: sharedConfig.Obfuscate{ Enabled: false, }, }, }, Export: "file", }, }, }, }, ) return policyTree } func mixedEndpointPolicyTree() *urltree.EndpointTree[config.EndpointPolicy] { policyTree, _ := config.BuildEndpointPolicyTree( []sharedConfig.EndpointConfig{ { Method: "GET", URL: "twitter.com/user/1234/messages", Diagnosis: []sharedConfig.Diagnosis{ { Name: "diagnosis1", Enabled: true, Config: sharedConfig.DiagnosisConfig{ HARExporter: &sharedConfig.HARExporterConfig{ TransactionMaxSize: 1000, Obfuscate: sharedConfig.Obfuscate{ Enabled: false, }, }, }, Export: "file", }, }, }, { Method: "GET", URL: "twitter.com/user/1234/posts", Remedies: []sharedConfig.Remedy{ { Name: "remedy1", Enabled: true, Config: sharedConfig.RemedyConfig{ FixedResponse: &sharedConfig.FixedResponseConfig{ StatusCode: 418, }, }, }, }, Diagnosis: []sharedConfig.Diagnosis{ { Name: "diagnosis1", Enabled: true, Config: sharedConfig.DiagnosisConfig{ HARExporter: &sharedConfig.HARExporterConfig{ TransactionMaxSize: 1000, Obfuscate: sharedConfig.Obfuscate{ Enabled: false, }, }, }, Export: "file", }, }, }, }, ) return policyTree } type mockWriter struct { messages string } func (writer *mockWriter) Write(b []byte) (int, error) { writer.messages += "\n" + string(b) return len(string(b)), nil } func (writer *mockWriter) Close() error { return nil } func newMockWriter() *mockWriter { return &mockWriter{} }

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/TheLunarCompany/lunar'

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