From afcd17c125de44a4bf5401836d3123869f669890 Mon Sep 17 00:00:00 2001 From: tanyamadaan Date: Fri, 21 Mar 2025 23:19:01 +0530 Subject: [PATCH] Handling forward routing rules using routing plugin --- .../implementation/router/cmd/plugin.go | 4 +- .../implementation/router/cmd/plugin_test.go | 38 +- pkg/plugin/implementation/router/router.go | 107 +++-- .../implementation/router/router_test.go | 403 +++++++++++------- .../router/testData/bap_caller.yaml | 25 ++ .../router/testData/bap_receiver.yaml | 20 + .../router/testData/bpp_caller.yaml | 23 + .../router/testData/bpp_receiver.yaml | 30 ++ 8 files changed, 449 insertions(+), 201 deletions(-) create mode 100644 pkg/plugin/implementation/router/testData/bap_caller.yaml create mode 100644 pkg/plugin/implementation/router/testData/bap_receiver.yaml create mode 100644 pkg/plugin/implementation/router/testData/bpp_caller.yaml create mode 100644 pkg/plugin/implementation/router/testData/bpp_receiver.yaml diff --git a/pkg/plugin/implementation/router/cmd/plugin.go b/pkg/plugin/implementation/router/cmd/plugin.go index 525e777..f38ffa9 100644 --- a/pkg/plugin/implementation/router/cmd/plugin.go +++ b/pkg/plugin/implementation/router/cmd/plugin.go @@ -18,9 +18,9 @@ func (rp RouterProvider) New(ctx context.Context, config map[string]string) (def } // Parse the routing_config key from the config map - routingConfig, ok := config["routing_config"] + routingConfig, ok := config["routingConfig"] if !ok { - return nil, nil, errors.New("routing_config is required in the configuration") + return nil, nil, errors.New("routingConfig is required in the configuration") } return router.New(ctx, &router.Config{ RoutingConfig: routingConfig, diff --git a/pkg/plugin/implementation/router/cmd/plugin_test.go b/pkg/plugin/implementation/router/cmd/plugin_test.go index a9af54a..afa0c97 100644 --- a/pkg/plugin/implementation/router/cmd/plugin_test.go +++ b/pkg/plugin/implementation/router/cmd/plugin_test.go @@ -13,17 +13,17 @@ func setupTestConfig(t *testing.T) string { t.Helper() // Create a temporary directory for the routing rules - configDir, err := os.MkdirTemp("", "routing_rules") + configDir, err := os.MkdirTemp("", "routingRules") if err != nil { t.Fatalf("Failed to create temp directory: %v", err) } // Define sample routing rules rulesContent := ` -routing_rules: +routingRules: - domain: "ONDC:TRV11" version: "2.0.0" - routing_type: "url" + routingType: "url" target: url: "https://services-backend/trv/v1" endpoints: @@ -34,7 +34,7 @@ routing_rules: - domain: "ONDC:TRV11" version: "2.0.0" - routing_type: "msgq" + routingType: "msgq" target: topic_id: "trv_topic_id1" endpoints: @@ -42,7 +42,7 @@ routing_rules: ` // Write the routing rules to a file - rulesFilePath := filepath.Join(configDir, "routing_rules.yaml") + rulesFilePath := filepath.Join(configDir, "routingRules.yaml") if err := os.WriteFile(rulesFilePath, []byte(rulesContent), 0644); err != nil { t.Fatalf("Failed to write routing rules file: %v", err) } @@ -50,8 +50,8 @@ routing_rules: return rulesFilePath } -// TestRouterProvider_Success tests the RouterProvider implementation for success cases. -func TestRouterProvider_Success(t *testing.T) { +// TestRouterProviderSuccess tests the RouterProvider implementation for success cases. +func TestRouterProviderSuccess(t *testing.T) { rulesFilePath := setupTestConfig(t) defer os.RemoveAll(filepath.Dir(rulesFilePath)) @@ -65,7 +65,7 @@ func TestRouterProvider_Success(t *testing.T) { name: "Valid configuration", ctx: context.Background(), config: map[string]string{ - "routing_config": rulesFilePath, + "routingConfig": rulesFilePath, }, }, } @@ -73,7 +73,7 @@ func TestRouterProvider_Success(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { provider := RouterProvider{} - router, closeFunc, err := provider.New(tt.ctx, tt.config) + router, _, err := provider.New(tt.ctx, tt.config) // Ensure no error occurred if err != nil { @@ -85,20 +85,12 @@ func TestRouterProvider_Success(t *testing.T) { if router == nil { t.Error("expected a non-nil Router instance, got nil") } - if closeFunc == nil { - t.Error("expected a non-nil close function, got nil") - } - - // Test the close function - if err := closeFunc(); err != nil { - t.Errorf("close function returned an error: %v", err) - } }) } } -// TestRouterProvider_Failure tests the RouterProvider implementation for failure cases. -func TestRouterProvider_Failure(t *testing.T) { +// TestRouterProviderFailure tests the RouterProvider implementation for failure cases. +func TestRouterProviderFailure(t *testing.T) { rulesFilePath := setupTestConfig(t) defer os.RemoveAll(filepath.Dir(rulesFilePath)) @@ -113,20 +105,20 @@ func TestRouterProvider_Failure(t *testing.T) { name: "Empty routing config path", ctx: context.Background(), config: map[string]string{ - "routing_config": "", + "routingConfig": "", }, - expectedError: "failed to load routing rules: routing_config path is empty", + expectedError: "failed to load routing rules: routingConfig path is empty", }, { name: "Missing routing config key", ctx: context.Background(), config: map[string]string{}, - expectedError: "routing_config is required in the configuration", + expectedError: "routingConfig is required in the configuration", }, { name: "Nil context", ctx: nil, - config: map[string]string{"routing_config": rulesFilePath}, + config: map[string]string{"routingConfig": rulesFilePath}, expectedError: "context cannot be nil", }, } diff --git a/pkg/plugin/implementation/router/router.go b/pkg/plugin/implementation/router/router.go index 040592b..2649dbd 100644 --- a/pkg/plugin/implementation/router/router.go +++ b/pkg/plugin/implementation/router/router.go @@ -16,12 +16,12 @@ import ( // Config holds the configuration for the Router plugin. type Config struct { - RoutingConfig string `json:"routing_config"` + RoutingConfig string `json:"routingConfig"` } // RoutingConfig represents the structure of the routing configuration file. type routingConfig struct { - RoutingRules []routingRule `yaml:"routing_rules"` + RoutingRules []routingRule `yaml:"routingRules"` } // Router implements Router interface @@ -34,18 +34,20 @@ type Router struct { type routingRule struct { Domain string `yaml:"domain"` Version string `yaml:"version"` - RoutingType string `yaml:"routing_type"` // "url" or "msgq" - Target target `yaml:"target"` + RoutingType string `yaml:"routingType"` // "url", "msgq", "bpp", or "bap" + Target target `yaml:"target,omitempty"` Endpoints []string `yaml:"endpoints"` } // Target contains destination-specific details. type target struct { - URL string `yaml:"url,omitempty"` // For "url" type + URL string `yaml:"url,omitempty"` // URL for "url" or gateway endpoint for "bpp"/"bap" TopicID string `yaml:"topic_id,omitempty"` // For "msgq" type } -// New initializes a new ProxyRouter instance. +// New initializes a new Router instance with the provided configuration. +// It loads and validates the routing rules from the specified YAML file. +// Returns an error if the configuration is invalid or the rules cannot be loaded. func New(ctx context.Context, config *Config) (*Router, func() error, error) { // Check if config is nil if config == nil { @@ -59,17 +61,17 @@ func New(ctx context.Context, config *Config) (*Router, func() error, error) { if err := router.loadRules(); err != nil { return nil, nil, fmt.Errorf("failed to load routing rules: %w", err) } - return router, router.Close, nil + return router, nil, nil } // LoadRules reads and parses routing rules from the YAML configuration file. func (r *Router) loadRules() error { if r.config.RoutingConfig == "" { - return fmt.Errorf("routing_config path is empty") + return fmt.Errorf("routingConfig path is empty") } data, err := os.ReadFile(r.config.RoutingConfig) if err != nil { - return fmt.Errorf("error reading config file: %w", err) + return fmt.Errorf("error reading config file at %s: %w", r.config.RoutingConfig, err) } var config routingConfig if err := yaml.Unmarshal(data, &config); err != nil { @@ -87,21 +89,29 @@ func (r *Router) loadRules() error { // validateRules performs basic validation on the loaded routing rules. func validateRules(rules []routingRule) error { for _, rule := range rules { + // Ensure domain, version, and routingType are present if rule.Domain == "" || rule.Version == "" || rule.RoutingType == "" { - return fmt.Errorf("invalid rule: domain, version, and routing_type are required") + return fmt.Errorf("invalid rule: domain, version, and routingType are required") } + // Validate based on routingType switch rule.RoutingType { case "url": if rule.Target.URL == "" { - return fmt.Errorf("invalid rule: url is required for routing_type 'url'") + return fmt.Errorf("invalid rule: url is required for routingType 'url'") + } + if _, err := url.ParseRequestURI(rule.Target.URL); err != nil { + return fmt.Errorf("invalid URL in rule: %w", err) } case "msgq": if rule.Target.TopicID == "" { - return fmt.Errorf("invalid rule: topic_id is required for routing_type 'msgq'") + return fmt.Errorf("invalid rule: topicId is required for routingType 'msgq'") } + case "bpp", "bap": + // No target validation needed for bpp/bap, as they use URIs from the request body + continue default: - return fmt.Errorf("invalid rule: unknown routing_type '%s'", rule.RoutingType) + return fmt.Errorf("invalid rule: unknown routingType '%s'", rule.RoutingType) } } return nil @@ -115,37 +125,80 @@ func (r *Router) Route(ctx context.Context, url *url.URL, body []byte) (*definit Context struct { Domain string `json:"domain"` Version string `json:"version"` + BppURI string `json:"bpp_uri,omitempty"` + BapURI string `json:"bap_uri,omitempty"` } `json:"context"` } if err := json.Unmarshal(body, &requestBody); err != nil { return nil, fmt.Errorf("error parsing request body: %w", err) } + // Extract the endpoint from the URL + endpoint := path.Base(url.Path) + + // Collect all matching rules for the domain and version + matchingRules := r.getMatchingRules(requestBody.Context.Domain, requestBody.Context.Version) + + // If no matching rules are found, return an error + if len(matchingRules) == 0 { + return nil, fmt.Errorf("no matching routing rule found for domain %s and version %s", requestBody.Context.Domain, requestBody.Context.Version) + } + // Match the rule - for _, rule := range r.rules { - if rule.Domain == requestBody.Context.Domain && rule.Version == requestBody.Context.Version { - // Check if the endpoint matches - endpoint := path.Base(url.Path) - for _, ep := range rule.Endpoints { - if strings.EqualFold(ep, endpoint) { + for _, rule := range matchingRules { + for _, ep := range rule.Endpoints { + if strings.EqualFold(ep, endpoint) { + switch rule.RoutingType { + case "msgq": return &definition.Route{ RoutingType: rule.RoutingType, TopicID: rule.Target.TopicID, + }, nil + case "url": + return &definition.Route{ + RoutingType: rule.RoutingType, TargetURL: rule.Target.URL, }, nil + case "bpp": + return handleRouting(rule, requestBody.Context.BppURI, endpoint, "bpp") + case "bap": + return handleRouting(rule, requestBody.Context.BapURI, endpoint, "bap") + default: + return nil, fmt.Errorf("unsupported routingType: %s", rule.RoutingType) } } - - // If domain and version match but endpoint is not found, return an error - return nil, fmt.Errorf("endpoint '%s' is not supported for domain %s and version %s", endpoint, requestBody.Context.Domain, requestBody.Context.Version) } } - // return nil, fmt.Errorf("no matching routing rule found for domain %s and version %s", requestBody.Context.Domain, requestBody.Context.Version) - return nil, fmt.Errorf("no matching routing rule found for domain %s and version %s", requestBody.Context.Domain, requestBody.Context.Version) + // If domain and version match but endpoint is not found, return an error + return nil, fmt.Errorf("endpoint '%s' is not supported for domain %s and version %s", endpoint, requestBody.Context.Domain, requestBody.Context.Version) } -// Close releases resources (mock implementation returning nil). -func (r *Router) Close() error { - return nil +// getMatchingRules returns all rules that match the given domain and version +func (r *Router) getMatchingRules(domain, version string) []routingRule { + var matchingRules []routingRule + for _, rule := range r.rules { + if rule.Domain == domain && rule.Version == version { + matchingRules = append(matchingRules, rule) + } + } + return matchingRules +} + +// handleRouting handles routing for bap and bpp routing type +func handleRouting(rule routingRule, uri, endpoint string, routingType string) (*definition.Route, error) { + if uri == "" { + if rule.Target.URL != "" { + return &definition.Route{ + RoutingType: routingType, + TargetURL: rule.Target.URL, + }, nil + } else { + return nil, fmt.Errorf("no target URI or URL found for %s routing type and %s endpoint", routingType, endpoint) + } + } + return &definition.Route{ + RoutingType: routingType, + TargetURL: uri, + }, nil } diff --git a/pkg/plugin/implementation/router/router_test.go b/pkg/plugin/implementation/router/router_test.go index 0593014..3d3fe70 100644 --- a/pkg/plugin/implementation/router/router_test.go +++ b/pkg/plugin/implementation/router/router_test.go @@ -2,6 +2,7 @@ package router import ( "context" + "embed" "net/url" "os" "path/filepath" @@ -9,8 +10,10 @@ import ( "testing" ) -// setupTestConfig creates a temporary directory and writes a sample routing rules file. -func setupTestConfig(t *testing.T) string { +//go:embed testData/* +var testData embed.FS + +func setupTestConfig(t *testing.T, yamlFileName string) string { t.Helper() // Create a temporary directory for the routing rules @@ -19,114 +22,124 @@ func setupTestConfig(t *testing.T) string { t.Fatalf("Failed to create temp directory: %v", err) } - // Define sample routing rules - rulesContent := ` -routing_rules: - - domain: "ONDC:TRV11" - version: "2.0.0" - routing_type: "url" - target: - url: "https://services-backend/trv/v1" - endpoints: - - select - - init - - confirm - - status - - - domain: "ONDC:TRV11" - version: "2.0.0" - routing_type: "msgq" - target: - topic_id: "trv_topic_id1" - endpoints: - - search -` + // Read the YAML file content + yamlContent := readYAMLFile(t, yamlFileName) // Write the routing rules to a file rulesFilePath := filepath.Join(configDir, "routing_rules.yaml") - if err := os.WriteFile(rulesFilePath, []byte(rulesContent), 0644); err != nil { + if err := os.WriteFile(rulesFilePath, []byte(yamlContent), 0644); err != nil { t.Fatalf("Failed to write routing rules file: %v", err) } return rulesFilePath } +func readYAMLFile(t *testing.T, fileName string) string { + t.Helper() + + // Read the YAML file + content, err := testData.ReadFile("testData/" + fileName) + if err != nil { + t.Fatalf("Failed to read YAML file: %v", err) + } + + return string(content) +} + +// setupRouter is a helper function to create router instance. +func setupRouter(t *testing.T, configFile string) (*Router, func() error, string) { + rulesFilePath := setupTestConfig(t, configFile) + config := &Config{ + RoutingConfig: rulesFilePath, + } + router, _, err := New(context.Background(), config) + if err != nil { + t.Fatalf("New failed: %v", err) + } + return router, nil, rulesFilePath +} + // TestNew tests the New function. func TestNew(t *testing.T) { ctx := context.Background() - rulesFilePath := setupTestConfig(t) - defer os.RemoveAll(filepath.Dir(rulesFilePath)) - // Define test cases - tests := []struct { - name string - config *Config - expectedError string - }{ - { - name: "Valid configuration", - config: &Config{ - RoutingConfig: rulesFilePath, - }, - expectedError: "", - }, - { - name: "Empty config", - config: nil, - expectedError: "config cannot be nil", - }, - { - name: "Empty routing config path", - config: &Config{ - RoutingConfig: "", - }, - expectedError: "routing_config path is empty", - }, - { - name: "Routing config file does not exist", - config: &Config{ - RoutingConfig: "/nonexistent/path/to/rules.yaml", - }, - expectedError: "error reading config file", - }, + // List of YAML files in the testData directory + yamlFiles := []string{ + "bap_caller.yaml", + "bap_receiver.yaml", + "bpp_caller.yaml", + "bpp_receiver.yaml", } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - router, closeFunc, err := New(ctx, tt.config) + for _, yamlFile := range yamlFiles { + t.Run(yamlFile, func(t *testing.T) { + rulesFilePath := setupTestConfig(t, yamlFile) + defer os.RemoveAll(filepath.Dir(rulesFilePath)) - // Check for expected error - if tt.expectedError != "" { - if err == nil || !strings.Contains(err.Error(), tt.expectedError) { - t.Errorf("expected error %q, got %v", tt.expectedError, err) - } - return + // Define test cases + tests := []struct { + name string + config *Config + expectedError string + }{ + { + name: "Valid configuration", + config: &Config{ + RoutingConfig: rulesFilePath, + }, + expectedError: "", + }, + { + name: "Empty config", + config: nil, + expectedError: "config cannot be nil", + }, + { + name: "Empty routing config path", + config: &Config{ + RoutingConfig: "", + }, + expectedError: "routingConfig path is empty", + }, + { + name: "Routing config file does not exist", + config: &Config{ + RoutingConfig: "/nonexistent/path/to/rules.yaml", + }, + expectedError: "error reading config file", + }, } - // Ensure no error occurred - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + router, _, err := New(ctx, tt.config) - // Ensure the router and close function are not nil - if router == nil { - t.Error("expected a non-nil Router instance, got nil") - } - if closeFunc == nil { - t.Error("expected a non-nil close function, got nil") - } + // Check for expected error + if tt.expectedError != "" { + if err == nil || !strings.Contains(err.Error(), tt.expectedError) { + t.Errorf("expected error %q, got %v", tt.expectedError, err) + } + return + } - // Test the close function - if err := closeFunc(); err != nil { - t.Errorf("close function returned an error: %v", err) + // Ensure no error occurred + if err != nil { + t.Errorf("unexpected error: %v", err) + return + } + + // Ensure the router and close function are not nil + if router == nil { + t.Error("expected a non-nil Router instance, got nil") + } + }) } }) } } -// TestValidateRules_Success tests the validate function for success cases. -func TestValidateRules_Success(t *testing.T) { +// TestValidateRulesSuccess tests the validate function for success cases. +func TestValidateRulesSuccess(t *testing.T) { tests := []struct { name string rules []routingRule @@ -135,13 +148,13 @@ func TestValidateRules_Success(t *testing.T) { name: "Valid rules with url routing", rules: []routingRule{ { - Domain: "example.com", + Domain: "retail", Version: "1.0.0", RoutingType: "url", Target: target{ URL: "https://example.com/api", }, - Endpoints: []string{"search", "select"}, + Endpoints: []string{"on_search", "on_select"}, }, }, }, @@ -149,13 +162,49 @@ func TestValidateRules_Success(t *testing.T) { name: "Valid rules with msgq routing", rules: []routingRule{ { - Domain: "example.com", + Domain: "retail", Version: "1.0.0", RoutingType: "msgq", Target: target{ TopicID: "example_topic", }, - Endpoints: []string{"search", "select"}, + Endpoints: []string{"on_search", "on_select"}, + }, + }, + }, + { + name: "Valid rules with bpp routing to gateway", + rules: []routingRule{ + { + Domain: "retail", + Version: "1.0.0", + RoutingType: "bpp", + Target: target{ + URL: "https://mock_gateway.com/api", + }, + Endpoints: []string{"search"}, + }, + }, + }, + { + name: "Valid rules with bpp routing", + rules: []routingRule{ + { + Domain: "retail", + Version: "1.0.0", + RoutingType: "bpp", + Endpoints: []string{"select"}, + }, + }, + }, + { + name: "Valid rules with bap routing", + rules: []routingRule{ + { + Domain: "retail", + Version: "1.0.0", + RoutingType: "bap", + Endpoints: []string{"select"}, }, }, }, @@ -171,8 +220,8 @@ func TestValidateRules_Success(t *testing.T) { } } -// TestValidateRules_Failure tests the validate function for failure cases. -func TestValidateRules_Failure(t *testing.T) { +// TestValidateRulesFailure tests the validate function for failure cases. +func TestValidateRulesFailure(t *testing.T) { tests := []struct { name string rules []routingRule @@ -190,13 +239,13 @@ func TestValidateRules_Failure(t *testing.T) { Endpoints: []string{"search", "select"}, }, }, - expectedErr: "invalid rule: domain, version, and routing_type are required", + expectedErr: "invalid rule: domain, version, and routingType are required", }, { name: "Missing version", rules: []routingRule{ { - Domain: "example.com", + Domain: "retail", RoutingType: "url", Target: target{ URL: "https://example.com/api", @@ -204,13 +253,13 @@ func TestValidateRules_Failure(t *testing.T) { Endpoints: []string{"search", "select"}, }, }, - expectedErr: "invalid rule: domain, version, and routing_type are required", + expectedErr: "invalid rule: domain, version, and routingType are required", }, { - name: "Missing routing_type", + name: "Missing routingType", rules: []routingRule{ { - Domain: "example.com", + Domain: "retail", Version: "1.0.0", Target: target{ URL: "https://example.com/api", @@ -218,28 +267,28 @@ func TestValidateRules_Failure(t *testing.T) { Endpoints: []string{"search", "select"}, }, }, - expectedErr: "invalid rule: domain, version, and routing_type are required", + expectedErr: "invalid rule: domain, version, and routingType are required", }, { - name: "Invalid routing_type", + name: "Invalid routingType", rules: []routingRule{ { - Domain: "example.com", + Domain: "retail", Version: "1.0.0", - RoutingType: "invalid_type", + RoutingType: "invalid", Target: target{ URL: "https://example.com/api", }, Endpoints: []string{"search", "select"}, }, }, - expectedErr: "invalid rule: unknown routing_type 'invalid_type'", + expectedErr: "invalid rule: unknown routingType 'invalid'", }, { - name: "Missing url for routing_type: url", + name: "Missing url for routingType: url", rules: []routingRule{ { - Domain: "example.com", + Domain: "retail", Version: "1.0.0", RoutingType: "url", Target: target{ @@ -248,13 +297,13 @@ func TestValidateRules_Failure(t *testing.T) { Endpoints: []string{"search", "select"}, }, }, - expectedErr: "invalid rule: url is required for routing_type 'url'", + expectedErr: "invalid rule: url is required for routingType 'url'", }, { - name: "Missing topic_id for routing_type: msgq", + name: "Missing topic_id for routingType: msgq", rules: []routingRule{ { - Domain: "example.com", + Domain: "retail", Version: "1.0.0", RoutingType: "msgq", Target: target{ @@ -263,7 +312,7 @@ func TestValidateRules_Failure(t *testing.T) { Endpoints: []string{"search", "select"}, }, }, - expectedErr: "invalid rule: topic_id is required for routing_type 'msgq'", + expectedErr: "invalid rule: topicId is required for routingType 'msgq'", }, } @@ -279,65 +328,63 @@ func TestValidateRules_Failure(t *testing.T) { } } -// TestRoute tests the Route function. -func TestRoute(t *testing.T) { +// TestRouteSuccess tests the Route function for success cases. +func TestRouteSuccess(t *testing.T) { ctx := context.Background() - rulesFilePath := setupTestConfig(t) - defer os.RemoveAll(filepath.Dir(rulesFilePath)) - config := &Config{ - RoutingConfig: rulesFilePath, - } - - router, closeFunc, err := New(ctx, config) - if err != nil { - t.Fatalf("New failed: %v", err) - } - defer func() { - if err := closeFunc(); err != nil { - t.Errorf("closeFunc failed: %v", err) - } - }() - - // Define test cases + // Define success test cases tests := []struct { - name string - url string - body string - expectedError string + name string + configFile string + url string + body string }{ { - name: "Valid domain, version, and endpoint", - url: "https://example.com/v1/ondc/select", - body: `{"context": {"domain": "ONDC:TRV11", "version": "2.0.0"}}`, + name: "Valid domain, version, and endpoint (bpp routing with gateway URL)", + configFile: "bap_caller.yaml", + url: "https://example.com/v1/ondc/search", + body: `{"context": {"domain": "ONDC:TRV10", "version": "2.0.0"}}`, }, { - name: "Unsupported endpoint", - url: "https://example.com/v1/ondc/unsupported", - body: `{"context": {"domain": "ONDC:TRV11", "version": "2.0.0"}}`, - expectedError: "endpoint 'unsupported' is not supported for domain ONDC:TRV11 and version 2.0.0", + name: "Valid domain, version, and endpoint (bpp routing with bpp_uri)", + configFile: "bap_caller.yaml", + url: "https://example.com/v1/ondc/select", + body: `{"context": {"domain": "ONDC:TRV10", "version": "2.0.0", "bpp_uri": "https://bpp1.example.com"}}`, }, { - name: "No matching rule", - url: "https://example.com/v1/ondc/select", - body: `{"context": {"domain": "ONDC:SRV11", "version": "2.0.0"}}`, - expectedError: "no matching routing rule found for domain ONDC:SRV11 and version 2.0.0", + name: "Valid domain, version, and endpoint (url routing)", + configFile: "bpp_receiver.yaml", + url: "https://example.com/v1/ondc/select", + body: `{"context": {"domain": "ONDC:TRV10", "version": "2.0.0"}}`, + }, + { + name: "Valid domain, version, and endpoint (msgq routing)", + configFile: "bpp_receiver.yaml", + url: "https://example.com/v1/ondc/search", + body: `{"context": {"domain": "ONDC:TRV10", "version": "2.0.0"}}`, + }, + { + name: "Valid domain, version, and endpoint (bap routing with bap_uri)", + configFile: "bpp_caller.yaml", + url: "https://example.com/v1/ondc/on_select", + body: `{"context": {"domain": "ONDC:TRV10", "version": "2.0.0", "bap_uri": "https://bap1.example.com"}}`, + }, + { + name: "Valid domain, version, and endpoint (bpp routing with bpp_uri)", + configFile: "bap_receiver.yaml", + url: "https://example.com/v1/ondc/on_select", + body: `{"context": {"domain": "ONDC:TRV10", "version": "2.0.0", "bpp_uri": "https://bpp1.example.com"}}`, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + router, _, rulesFilePath := setupRouter(t, tt.configFile) + defer os.RemoveAll(filepath.Dir(rulesFilePath)) + parsedURL, _ := url.Parse(tt.url) _, err := router.Route(ctx, parsedURL, []byte(tt.body)) - // Check for expected error - if tt.expectedError != "" { - if err == nil || !strings.Contains(err.Error(), tt.expectedError) { - t.Errorf("expected error %q, got %v", tt.expectedError, err) - } - return - } - // Ensure no error occurred if err != nil { t.Errorf("unexpected error: %v", err) @@ -345,3 +392,61 @@ func TestRoute(t *testing.T) { }) } } + +// TestRouteFailure tests the Route function for failure cases. +func TestRouteFailure(t *testing.T) { + ctx := context.Background() + + // Define failure test cases + tests := []struct { + name string + configFile string + url string + body string + expectedError string + }{ + { + name: "Unsupported endpoint", + configFile: "bpp_receiver.yaml", + url: "https://example.com/v1/ondc/unsupported", + body: `{"context": {"domain": "ONDC:TRV11", "version": "2.0.0"}}`, + expectedError: "endpoint 'unsupported' is not supported for domain ONDC:TRV11 and version 2.0.0", + }, + { + name: "No matching rule", + configFile: "bpp_receiver.yaml", + url: "https://example.com/v1/ondc/select", + body: `{"context": {"domain": "ONDC:SRV11", "version": "2.0.0"}}`, + expectedError: "no matching routing rule found for domain ONDC:SRV11 and version 2.0.0", + }, + { + name: "Missing bap_uri for bap routing", + configFile: "bpp_caller.yaml", + url: "https://example.com/v1/ondc/on_search", + body: `{"context": {"domain": "ONDC:TRV10", "version": "2.0.0"}}`, + expectedError: "no target URI or URL found for bap routing type and on_search endpoint", + }, + { + name: "Missing bpp_uri for bpp routing", + configFile: "bap_caller.yaml", + url: "https://example.com/v1/ondc/select", + body: `{"context": {"domain": "ONDC:TRV10", "version": "2.0.0"}}`, + expectedError: "no target URI or URL found for bpp routing type and select endpoint", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + router, _, rulesFilePath := setupRouter(t, tt.configFile) + defer os.RemoveAll(filepath.Dir(rulesFilePath)) + + parsedURL, _ := url.Parse(tt.url) + _, err := router.Route(ctx, parsedURL, []byte(tt.body)) + + // Check for expected error + if err == nil || !strings.Contains(err.Error(), tt.expectedError) { + t.Errorf("expected error %q, got %v", tt.expectedError, err) + } + }) + } +} diff --git a/pkg/plugin/implementation/router/testData/bap_caller.yaml b/pkg/plugin/implementation/router/testData/bap_caller.yaml new file mode 100644 index 0000000..b1d5a44 --- /dev/null +++ b/pkg/plugin/implementation/router/testData/bap_caller.yaml @@ -0,0 +1,25 @@ +routingRules: + - domain: "ONDC:TRV10" + version: "2.0.0" + routingType: "bpp" + target: + url: "https://gateway.example.com" + endpoints: + - search + - domain: "ONDC:TRV10" + version: "2.0.0" + routingType: "bpp" + endpoints: + - select + - init + - confirm + - status + - cancel + - domain: "ONDC:TRV12" + version: "2.0.0" + routingType: "bpp" + endpoints: + - select + - init + - confirm + - status \ No newline at end of file diff --git a/pkg/plugin/implementation/router/testData/bap_receiver.yaml b/pkg/plugin/implementation/router/testData/bap_receiver.yaml new file mode 100644 index 0000000..ca4a478 --- /dev/null +++ b/pkg/plugin/implementation/router/testData/bap_receiver.yaml @@ -0,0 +1,20 @@ +routingRules: + - domain: "ONDC:TRV10" + version: "2.0.0" + routingType: "url" + target: + url: "https://services-backend/trv/v1" + endpoints: + - on_select + - on_init + - on_confirm + - on_status + - on_update + - on_cancel + - domain: "ONDC:TRV10" + version: "2.0.0" + routingType: "msgq" + target: + topic_id: "trv_topic_id1" + endpoints: + - on_search \ No newline at end of file diff --git a/pkg/plugin/implementation/router/testData/bpp_caller.yaml b/pkg/plugin/implementation/router/testData/bpp_caller.yaml new file mode 100644 index 0000000..0d9a670 --- /dev/null +++ b/pkg/plugin/implementation/router/testData/bpp_caller.yaml @@ -0,0 +1,23 @@ +routingRules: + - domain: "ONDC:TRV10" + version: "2.0.0" + routingType: "bap" + endpoints: + - on_search + - on_select + - on_init + - on_confirm + - on_status + - on_update + - on_cancel + - domain: "ONDC:TRV11" + version: "2.0.0" + routingType: "bap" + endpoints: + - on_search + - on_select + - on_init + - on_confirm + - on_status + - on_update + - on_cancel \ No newline at end of file diff --git a/pkg/plugin/implementation/router/testData/bpp_receiver.yaml b/pkg/plugin/implementation/router/testData/bpp_receiver.yaml new file mode 100644 index 0000000..6febce6 --- /dev/null +++ b/pkg/plugin/implementation/router/testData/bpp_receiver.yaml @@ -0,0 +1,30 @@ +routingRules: + - domain: "ONDC:TRV10" + version: "2.0.0" + routingType: "url" + target: + url: "https://services-backend/trv/v1" + endpoints: + - select + - init + - confirm + - status + - cancel + + - domain: "ONDC:TRV10" + version: "2.0.0" + routingType: "msgq" + target: + topic_id: "trv_topic_id1" + endpoints: + - search + + - domain: "ONDC:TRV11" + version: "2.0.0" + routingType: "url" + target: + url: "https://services-backend/trv/v1" + endpoints: + - select + - init + - confirm \ No newline at end of file