Changes as per integration testing

This commit is contained in:
tanyamadaan
2025-03-28 12:20:08 +05:30
parent 086375e063
commit dff7a5abb3
6 changed files with 146 additions and 53 deletions

View File

@@ -9,7 +9,7 @@ import (
type Route struct { type Route struct {
TargetType string // "url" or "msgq" or "bap" or "bpp" TargetType string // "url" or "msgq" or "bap" or "bpp"
PublisherID string // For message queues PublisherID string // For message queues
URL string // For API calls URL *url.URL // For API calls
} }
// RouterProvider initializes the a new Router instance with the given config. // RouterProvider initializes the a new Router instance with the given config.

View File

@@ -28,4 +28,4 @@ func (rp RouterProvider) New(ctx context.Context, config map[string]string) (def
} }
// Provider is the exported symbol that the plugin manager will look for. // Provider is the exported symbol that the plugin manager will look for.
var Provider definition.RouterProvider = RouterProvider{} var Provider = RouterProvider{}

View File

@@ -33,7 +33,7 @@ type Router struct {
type routingRule struct { type routingRule struct {
Domain string `yaml:"domain"` Domain string `yaml:"domain"`
Version string `yaml:"version"` Version string `yaml:"version"`
TargetType string `yaml:"targetType"` // "url", "msgq", "bpp", or "bap" TargetType string `yaml:"targetType"` // "url", "publisher", "bpp", or "bap"
Target target `yaml:"target,omitempty"` Target target `yaml:"target,omitempty"`
Endpoints []string `yaml:"endpoints"` Endpoints []string `yaml:"endpoints"`
} }
@@ -47,7 +47,7 @@ type target struct {
// TargetType defines possible target destinations. // TargetType defines possible target destinations.
const ( const (
targetTypeURL = "url" // Route to a specific URL targetTypeURL = "url" // Route to a specific URL
targetTypeMSGQ = "msgq" // Route to a message queue targetTypePublisher = "publisher" // Route to a publisher
targetTypeBPP = "bpp" // Route to a BPP endpoint targetTypeBPP = "bpp" // Route to a BPP endpoint
targetTypeBAP = "bap" // Route to a BAP endpoint targetTypeBAP = "bap" // Route to a BAP endpoint
) )
@@ -71,6 +71,30 @@ func New(ctx context.Context, config *Config) (*Router, func() error, error) {
return router, nil, nil return router, nil, nil
} }
// parseTargetURL parses a URL string into a url.URL object with strict validation
func parseTargetURL(urlStr string) (*url.URL, error) {
if urlStr == "" {
return nil, nil
}
parsed, err := url.Parse(urlStr)
if err != nil {
return nil, fmt.Errorf("invalid URL '%s': %w", urlStr, err)
}
// Enforce scheme requirement
if parsed.Scheme == "" {
return nil, fmt.Errorf("URL '%s' must include a scheme (http/https)", urlStr)
}
// Optionally validate scheme is http or https
if parsed.Scheme != "https" {
return nil, fmt.Errorf("URL '%s' must use https scheme", urlStr)
}
return parsed, nil
}
// LoadRules reads and parses routing rules from the YAML configuration file. // LoadRules reads and parses routing rules from the YAML configuration file.
func (r *Router) loadRules(configPath string) error { func (r *Router) loadRules(configPath string) error {
if configPath == "" { if configPath == "" {
@@ -105,25 +129,33 @@ func (r *Router) loadRules(configPath string) error {
for _, endpoint := range rule.Endpoints { for _, endpoint := range rule.Endpoints {
var route *definition.Route var route *definition.Route
switch rule.TargetType { switch rule.TargetType {
case targetTypeMSGQ: case targetTypePublisher:
route = &definition.Route{ route = &definition.Route{
TargetType: rule.TargetType, TargetType: rule.TargetType,
PublisherID: rule.Target.PublisherID, PublisherID: rule.Target.PublisherID,
} }
case targetTypeURL: case targetTypeURL:
parsedURL, err := parseTargetURL(rule.Target.URL)
if err != nil {
return fmt.Errorf("invalid URL in rule: %w", err)
}
route = &definition.Route{ route = &definition.Route{
TargetType: rule.TargetType, TargetType: rule.TargetType,
URL: rule.Target.URL, URL: parsedURL,
} }
case targetTypeBPP, targetTypeBAP: case targetTypeBPP, targetTypeBAP:
var parsedURL *url.URL
if rule.Target.URL != "" {
parsedURL, err = parseTargetURL(rule.Target.URL)
if err != nil {
return fmt.Errorf("invalid URL in rule: %w", err)
}
}
route = &definition.Route{ route = &definition.Route{
TargetType: rule.TargetType, TargetType: rule.TargetType,
URL: rule.Target.URL, // Fallback URL if URI not provided in request URL: parsedURL,
} }
} }
fmt.Print(r.rules)
r.rules[rule.Domain][rule.Version][endpoint] = route r.rules[rule.Domain][rule.Version][endpoint] = route
} }
} }
@@ -145,15 +177,19 @@ func validateRules(rules []routingRule) error {
if rule.Target.URL == "" { if rule.Target.URL == "" {
return fmt.Errorf("invalid rule: url is required for targetType 'url'") return fmt.Errorf("invalid rule: url is required for targetType 'url'")
} }
if _, err := url.ParseRequestURI(rule.Target.URL); err != nil { if _, err := parseTargetURL(rule.Target.URL); err != nil {
return fmt.Errorf("invalid URL in rule: %w", err) return fmt.Errorf("invalid URL - %s: %w", rule.Target.URL, err)
} }
case targetTypeMSGQ: case targetTypePublisher:
if rule.Target.PublisherID == "" { if rule.Target.PublisherID == "" {
return fmt.Errorf("invalid rule: publisherID is required for targetType 'msgq'") return fmt.Errorf("invalid rule: publisherID is required for targetType 'publisher'")
} }
case targetTypeBPP, targetTypeBAP: case targetTypeBPP, targetTypeBAP:
// No target validation needed for bpp/bap, as they use URIs from the request body if rule.Target.URL != "" {
if _, err := parseTargetURL(rule.Target.URL); err != nil {
return fmt.Errorf("invalid URL - %s defined in routing config for target type %s: %w", rule.Target.URL, rule.TargetType, err)
}
}
continue continue
default: default:
return fmt.Errorf("invalid rule: unknown targetType '%s'", rule.TargetType) return fmt.Errorf("invalid rule: unknown targetType '%s'", rule.TargetType)
@@ -197,36 +233,43 @@ func (r *Router) Route(ctx context.Context, url *url.URL, body []byte) (*definit
return nil, fmt.Errorf("endpoint '%s' is not supported for domain %s and version %s in routing config", return nil, fmt.Errorf("endpoint '%s' is not supported for domain %s and version %s in routing config",
endpoint, requestBody.Context.Domain, requestBody.Context.Version) endpoint, requestBody.Context.Domain, requestBody.Context.Version)
} }
// Handle BPP/BAP routing with request URIs // Handle BPP/BAP routing with request URIs
switch route.TargetType { switch route.TargetType {
case targetTypeBPP: case targetTypeBPP:
uri := strings.TrimSpace(requestBody.Context.BPPURI) return handleProtocolMapping(route, requestBody.Context.BPPURI, endpoint)
target := strings.TrimSpace(route.URL)
if len(uri) != 0 {
target = uri
}
if len(target) == 0 {
return nil, fmt.Errorf("could not determine destination for endpoint '%s': neither request contained a BPP URI nor was a default URL configured in routing rules", endpoint)
}
route = &definition.Route{
TargetType: route.TargetType,
URL: target,
}
case targetTypeBAP: case targetTypeBAP:
uri := strings.TrimSpace(requestBody.Context.BAPURI) return handleProtocolMapping(route, requestBody.Context.BAPURI, endpoint)
target := strings.TrimSpace(route.URL) }
return route, nil
}
// handleProtocolMapping handles both BPP and BAP routing with proper URL construction
func handleProtocolMapping(route *definition.Route, requestURI, endpoint string) (*definition.Route, error) {
uri := strings.TrimSpace(requestURI)
var targetURL *url.URL
if len(uri) != 0 { if len(uri) != 0 {
target = uri parsedURL, err := parseTargetURL(uri)
if err != nil {
return nil, fmt.Errorf("invalid %s URI - %s in request body for %s: %w", strings.ToUpper(route.TargetType), uri, endpoint, err)
} }
if len(target) == 0 { targetURL = parsedURL
return nil, fmt.Errorf("could not determine destination for endpoint '%s': neither request contained a BAP URI nor was a default URL configured in routing rules", endpoint)
} }
route = &definition.Route{
TargetType: route.TargetType, // If no request URI, fall back to configured URL with endpoint appended
URL: target, if targetURL == nil {
if route.URL == nil {
return nil, fmt.Errorf("could not determine destination for endpoint '%s': neither request contained a %s URI nor was a default URL configured in routing rules", endpoint, strings.ToUpper(route.TargetType))
}
targetURL = &url.URL{
Scheme: route.URL.Scheme,
Host: route.URL.Host,
Path: path.Join(route.URL.Path, endpoint),
} }
} }
return route, nil return &definition.Route{
TargetType: targetTypeURL,
URL: targetURL,
}, nil
} }

View File

@@ -143,12 +143,12 @@ func TestValidateRulesSuccess(t *testing.T) {
}, },
}, },
{ {
name: "Valid rules with msgq routing", name: "Valid rules with publisher routing",
rules: []routingRule{ rules: []routingRule{
{ {
Domain: "retail", Domain: "retail",
Version: "1.0.0", Version: "1.0.0",
TargetType: "msgq", TargetType: "publisher",
Target: target{ Target: target{
PublisherID: "example_topic", PublisherID: "example_topic",
}, },
@@ -284,19 +284,64 @@ func TestValidateRulesFailure(t *testing.T) {
wantErr: "invalid rule: url is required for targetType 'url'", wantErr: "invalid rule: url is required for targetType 'url'",
}, },
{ {
name: "Missing topic_id for targetType: msgq", name: "Invalid URL format for targetType: url",
rules: []routingRule{ rules: []routingRule{
{ {
Domain: "retail", Domain: "retail",
Version: "1.0.0", Version: "1.0.0",
TargetType: "msgq", TargetType: "url",
Target: target{
URL: "htp://invalid-url.com", // Invalid scheme
},
Endpoints: []string{"search"},
},
},
wantErr: "invalid URL - htp://invalid-url.com: URL 'htp://invalid-url.com' must use https scheme",
},
{
name: "Missing topic_id for targetType: publisher",
rules: []routingRule{
{
Domain: "retail",
Version: "1.0.0",
TargetType: "publisher",
Target: target{ Target: target{
// PublisherID is missing // PublisherID is missing
}, },
Endpoints: []string{"search", "select"}, Endpoints: []string{"search", "select"},
}, },
}, },
wantErr: "invalid rule: publisherID is required for targetType 'msgq'", wantErr: "invalid rule: publisherID is required for targetType 'publisher'",
},
{
name: "Invalid URL for BPP targetType",
rules: []routingRule{
{
Domain: "retail",
Version: "1.0.0",
TargetType: "bpp",
Target: target{
URL: "htp://invalid-url.com", // Invalid URL
},
Endpoints: []string{"search"},
},
},
wantErr: "invalid URL - htp://invalid-url.com defined in routing config for target type bpp",
},
{
name: "Invalid URL for BAP targetType",
rules: []routingRule{
{
Domain: "retail",
Version: "1.0.0",
TargetType: "bap",
Target: target{
URL: "http://[invalid].com", // Invalid host
},
Endpoints: []string{"search"},
},
},
wantErr: "invalid URL - http://[invalid].com defined in routing config for target type bap",
}, },
} }
@@ -340,7 +385,7 @@ func TestRouteSuccess(t *testing.T) {
body: `{"context": {"domain": "ONDC:TRV10", "version": "2.0.0"}}`, body: `{"context": {"domain": "ONDC:TRV10", "version": "2.0.0"}}`,
}, },
{ {
name: "Valid domain, version, and endpoint (msgq routing)", name: "Valid domain, version, and endpoint (publisher routing)",
configFile: "bpp_receiver.yaml", configFile: "bpp_receiver.yaml",
url: "https://example.com/v1/ondc/search", url: "https://example.com/v1/ondc/search",
body: `{"context": {"domain": "ONDC:TRV10", "version": "2.0.0"}}`, body: `{"context": {"domain": "ONDC:TRV10", "version": "2.0.0"}}`,
@@ -415,6 +460,13 @@ func TestRouteFailure(t *testing.T) {
body: `{"context": {"domain": "ONDC:TRV10", "version": "2.0.0"}}`, body: `{"context": {"domain": "ONDC:TRV10", "version": "2.0.0"}}`,
wantErr: "could not determine destination for endpoint 'select': neither request contained a BPP URI nor was a default URL configured in routing rules", wantErr: "could not determine destination for endpoint 'select': neither request contained a BPP URI nor was a default URL configured in routing rules",
}, },
{
name: "Invalid bpp_uri format in request",
configFile: "bap_caller.yaml",
url: "https://example.com/v1/ondc/select",
body: `{"context": {"domain": "ONDC:TRV10", "version": "2.0.0", "bpp_uri": "htp://invalid-url"}}`, // Invalid scheme (htp instead of http)
wantErr: "invalid BPP URI - htp://invalid-url in request body for select: URL 'htp://invalid-url' must use https scheme",
},
} }
for _, tt := range tests { for _, tt := range tests {

View File

@@ -13,7 +13,7 @@ routingRules:
- on_cancel - on_cancel
- domain: "ONDC:TRV10" - domain: "ONDC:TRV10"
version: "2.0.0" version: "2.0.0"
targetType: "msgq" targetType: "publisher"
target: target:
publisherId: "trv_topic_id1" publisherId: "trv_topic_id1"
endpoints: endpoints:

View File

@@ -10,15 +10,13 @@ routingRules:
- confirm - confirm
- status - status
- cancel - cancel
- domain: "ONDC:TRV10" - domain: "ONDC:TRV10"
version: "2.0.0" version: "2.0.0"
targetType: "msgq" targetType: "publisher"
target: target:
publisherId: "trv_topic_id1" publisherId: "trv_topic_id1"
endpoints: endpoints:
- search - search
- domain: "ONDC:TRV11" - domain: "ONDC:TRV11"
version: "2.0.0" version: "2.0.0"
targetType: "url" targetType: "url"