From 86bffa0844ad70927b4bf0d004ef3523f211dfc0 Mon Sep 17 00:00:00 2001 From: "mayur.popli" Date: Thu, 20 Mar 2025 08:41:21 +0530 Subject: [PATCH 01/21] feat: req preprocessor --- .../requestPreProcessor/reqpreprocessor.go | 100 ++++++++++++ .../reqpreprocessor_test.go | 143 ++++++++++++++++++ 2 files changed, 243 insertions(+) create mode 100644 shared/plugin/implementation/requestPreProcessor/reqpreprocessor.go create mode 100644 shared/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go diff --git a/shared/plugin/implementation/requestPreProcessor/reqpreprocessor.go b/shared/plugin/implementation/requestPreProcessor/reqpreprocessor.go new file mode 100644 index 0000000..177aecc --- /dev/null +++ b/shared/plugin/implementation/requestPreProcessor/reqpreprocessor.go @@ -0,0 +1,100 @@ +package reqpreprocessor + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + + "github.com/google/uuid" +) + +type Config struct { + checkKeys []string +} + +const contextKey = "context" + +func NewUUIDSetter(cfg *Config) (func(http.Handler) http.Handler, error) { + if err := validateConfig(cfg); err != nil { + return nil, err + } + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + + var data map[string]any + body, err := io.ReadAll(r.Body) + if err != nil { + http.Error(w, "Failed to read request body", http.StatusInternalServerError) + return + } + if err := json.Unmarshal(body, &data); err != nil { + http.Error(w, "Failed to decode request body", http.StatusBadRequest) + return + } + contextRaw := data[contextKey] + if contextRaw == nil { + http.Error(w, fmt.Sprintf("%s field not found.", contextKey), http.StatusBadRequest) + return + } + contextData, ok := contextRaw.(map[string]any) + if !ok { + http.Error(w, fmt.Sprintf("%s field is not a map.", contextKey), http.StatusBadRequest) + return + } + ctx := r.Context() + for _, key := range cfg.checkKeys { + value := uuid.NewString() + updatedValue := update(contextData, key, value) + ctx = context.WithValue(ctx, key, updatedValue) + } + data[contextKey] = contextData + updatedBody, err := json.Marshal(data) + if err != nil { + http.Error(w, "Failed to marshal updated JSON", http.StatusInternalServerError) + return + } + r.Body = io.NopCloser(bytes.NewBuffer(updatedBody)) + r.ContentLength = int64(len(updatedBody)) + r = r.WithContext(ctx) + next.ServeHTTP(w, r) + }) + }, nil +} + +func update(wrapper map[string]any, key string, value any) any { + field, exists := wrapper[key] + if !exists || isEmpty(field) { + wrapper[key] = value + return value + } + return field +} +func isEmpty(v any) bool { + switch v := v.(type) { + case string: + return v == "" + case nil: + return true + default: + return false + } +} + +func validateConfig(cfg *Config) error { + if cfg == nil { + return errors.New("config cannot be nil") + } + if len(cfg.checkKeys) == 0 { + return errors.New("checkKeys cannot be empty") + } + for _, key := range cfg.checkKeys { + if key == "" { + return errors.New("checkKeys cannot contain empty strings") + } + } + return nil +} diff --git a/shared/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go b/shared/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go new file mode 100644 index 0000000..47e46e0 --- /dev/null +++ b/shared/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go @@ -0,0 +1,143 @@ +package reqpreprocessor + +import ( + "bytes" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "testing" +) + +func TestNewUUIDSetter(t *testing.T) { + tests := []struct { + name string + config *Config + requestBody map[string]any + expectedCode int + expectedKeys []string + }{ + { + name: "Valid keys, update missing keys", + config: &Config{ + checkKeys: []string{"transaction_id", "message_id"}, + }, + requestBody: map[string]any{ + "context": map[string]any{ + "transaction_id": "", + "message_id": nil, + }, + }, + expectedCode: http.StatusOK, + expectedKeys: []string{"transaction_id", "message_id"}, + }, + { + name: "Valid keys, do not update existing keys", + config: &Config{ + checkKeys: []string{"transaction_id", "message_id"}, + }, + requestBody: map[string]any{ + "context": map[string]any{ + "transaction_id": "existing-transaction", + "message_id": "existing-message", + }, + }, + expectedCode: http.StatusOK, + expectedKeys: []string{"transaction_id", "message_id"}, + }, + { + name: "Missing context key", + config: &Config{ + checkKeys: []string{"transaction_id"}, + }, + requestBody: map[string]any{ + "otherKey": "value", + }, + expectedCode: http.StatusBadRequest, + }, + { + name: "Invalid context type", + config: &Config{ + checkKeys: []string{"transaction_id"}, + }, + requestBody: map[string]any{ + "context": "not-a-map", + }, + expectedCode: http.StatusBadRequest, + }, + { + name: "Empty checkKeys in config", + config: &Config{ + checkKeys: []string{}, + }, + requestBody: map[string]any{ + "context": map[string]any{ + "transaction_id": "", + }, + }, + expectedCode: http.StatusInternalServerError, + }, + { + name: "Nil config", + config: nil, + requestBody: map[string]any{}, + expectedCode: http.StatusInternalServerError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + middleware, err := NewUUIDSetter(tt.config) + if tt.config == nil || len(tt.config.checkKeys) == 0 { + if err == nil { + t.Fatal("Expected an error, but got none") + } + return + } + if err != nil { + t.Fatalf("Unexpected error while creating middleware: %v", err) + } + + // Prepare request + bodyBytes, _ := json.Marshal(tt.requestBody) + req := httptest.NewRequest(http.MethodPost, "/test", bytes.NewReader(bodyBytes)) + req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() + + // Define a dummy handler + dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + io.Copy(w, r.Body) + }) + + // Apply middleware + middleware(dummyHandler).ServeHTTP(rec, req) + + // Check status code + if rec.Code != tt.expectedCode { + t.Errorf("Expected status code %d, but got %d", tt.expectedCode, rec.Code) + } + + // If success, check updated keys + if rec.Code == http.StatusOK { + var responseBody map[string]any + if err := json.Unmarshal(rec.Body.Bytes(), &responseBody); err != nil { + t.Fatal("Failed to unmarshal response body:", err) + } + + // Validate updated keys + contextData, ok := responseBody[contextKey].(map[string]any) + if !ok { + t.Fatalf("Expected context to be a map, got %T", responseBody[contextKey]) + } + + for _, key := range tt.expectedKeys { + value, exists := contextData[key] + if !exists || isEmpty(value) { + t.Errorf("Expected key %s to be set, but it's missing or empty", key) + } + } + } + }) + } +} From bc316ed39fa6f9f0f9d58074f83dc9bcb247432b Mon Sep 17 00:00:00 2001 From: "mayur.popli" Date: Thu, 20 Mar 2025 08:47:30 +0530 Subject: [PATCH 02/21] feat: req preprocessor --- go.mod | 2 ++ go.sum | 2 ++ 2 files changed, 4 insertions(+) diff --git a/go.mod b/go.mod index 67f3590..5a545b8 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,8 @@ toolchain go1.23.7 require golang.org/x/crypto v0.36.0 +require github.com/google/uuid v1.6.0 // indirect + require ( golang.org/x/sys v0.31.0 // indirect gopkg.in/yaml.v2 v2.4.0 diff --git a/go.sum b/go.sum index d05e730..54090b4 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= From aa10fcceaf254144621bff794ba4089028a1467a Mon Sep 17 00:00:00 2001 From: "mayur.popli" Date: Thu, 20 Mar 2025 09:03:44 +0530 Subject: [PATCH 03/21] feat: req preprocessor --- .../implementation/requestPreProcessor/reqpreprocessor.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shared/plugin/implementation/requestPreProcessor/reqpreprocessor.go b/shared/plugin/implementation/requestPreProcessor/reqpreprocessor.go index 177aecc..8dcb231 100644 --- a/shared/plugin/implementation/requestPreProcessor/reqpreprocessor.go +++ b/shared/plugin/implementation/requestPreProcessor/reqpreprocessor.go @@ -16,6 +16,8 @@ type Config struct { checkKeys []string } +type contextKeyType string + const contextKey = "context" func NewUUIDSetter(cfg *Config) (func(http.Handler) http.Handler, error) { @@ -49,7 +51,7 @@ func NewUUIDSetter(cfg *Config) (func(http.Handler) http.Handler, error) { for _, key := range cfg.checkKeys { value := uuid.NewString() updatedValue := update(contextData, key, value) - ctx = context.WithValue(ctx, key, updatedValue) + ctx = context.WithValue(ctx, contextKeyType(key), updatedValue) } data[contextKey] = contextData updatedBody, err := json.Marshal(data) From 5e3422fb24ef4d65b1b218248f80f818c6570bdc Mon Sep 17 00:00:00 2001 From: "mayur.popli" Date: Thu, 20 Mar 2025 09:07:43 +0530 Subject: [PATCH 04/21] feat: req preprocessor --- .../requestPreProcessor/reqpreprocessor_test.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/shared/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go b/shared/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go index 47e46e0..483714d 100644 --- a/shared/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go +++ b/shared/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go @@ -24,8 +24,8 @@ func TestNewUUIDSetter(t *testing.T) { }, requestBody: map[string]any{ "context": map[string]any{ - "transaction_id": "", - "message_id": nil, + "transaction_id": "", + "message_id": nil, }, }, expectedCode: http.StatusOK, @@ -38,8 +38,8 @@ func TestNewUUIDSetter(t *testing.T) { }, requestBody: map[string]any{ "context": map[string]any{ - "transaction_id": "existing-transaction", - "message_id": "existing-message", + "transaction_id": "existing-transaction", + "message_id": "existing-message", }, }, expectedCode: http.StatusOK, @@ -107,7 +107,10 @@ func TestNewUUIDSetter(t *testing.T) { // Define a dummy handler dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) - io.Copy(w, r.Body) + if _, err := io.Copy(w, r.Body); err != nil { + http.Error(w, "Failed to copy request body", http.StatusInternalServerError) + return + } }) // Apply middleware From 02fb7c0897c83971bdc6ecfcb53605ff967ae0b9 Mon Sep 17 00:00:00 2001 From: "mayur.popli" Date: Fri, 21 Mar 2025 04:44:58 +0530 Subject: [PATCH 05/21] feat: added role for bap and bpp --- .../requestPreProcessor/reqpreprocessor.go | 15 +++-- .../reqpreprocessor_test.go | 66 ++++++++----------- 2 files changed, 38 insertions(+), 43 deletions(-) diff --git a/shared/plugin/implementation/requestPreProcessor/reqpreprocessor.go b/shared/plugin/implementation/requestPreProcessor/reqpreprocessor.go index 8dcb231..f5964c2 100644 --- a/shared/plugin/implementation/requestPreProcessor/reqpreprocessor.go +++ b/shared/plugin/implementation/requestPreProcessor/reqpreprocessor.go @@ -14,11 +14,12 @@ import ( type Config struct { checkKeys []string + Role string } - type contextKeyType string const contextKey = "context" +const subscriberIDKey contextKeyType = "subscriber_id" func NewUUIDSetter(cfg *Config) (func(http.Handler) http.Handler, error) { if err := validateConfig(cfg); err != nil { @@ -47,7 +48,14 @@ func NewUUIDSetter(cfg *Config) (func(http.Handler) http.Handler, error) { http.Error(w, fmt.Sprintf("%s field is not a map.", contextKey), http.StatusBadRequest) return } - ctx := r.Context() + var subID any + switch cfg.Role { + case "bap": + subID = contextData["bap_id"] + case "bpp": + subID = contextData["bpp_id"] + } + ctx := context.WithValue(r.Context(), subscriberIDKey, subID) for _, key := range cfg.checkKeys { value := uuid.NewString() updatedValue := update(contextData, key, value) @@ -90,9 +98,6 @@ func validateConfig(cfg *Config) error { if cfg == nil { return errors.New("config cannot be nil") } - if len(cfg.checkKeys) == 0 { - return errors.New("checkKeys cannot be empty") - } for _, key := range cfg.checkKeys { if key == "" { return errors.New("checkKeys cannot contain empty strings") diff --git a/shared/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go b/shared/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go index 483714d..85001df 100644 --- a/shared/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go +++ b/shared/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go @@ -3,7 +3,6 @@ package reqpreprocessor import ( "bytes" "encoding/json" - "io" "net/http" "net/http/httptest" "testing" @@ -16,34 +15,41 @@ func TestNewUUIDSetter(t *testing.T) { requestBody map[string]any expectedCode int expectedKeys []string + role string }{ { - name: "Valid keys, update missing keys", + name: "Valid keys, update missing keys with bap role", config: &Config{ checkKeys: []string{"transaction_id", "message_id"}, + Role: "bap", }, requestBody: map[string]any{ "context": map[string]any{ "transaction_id": "", "message_id": nil, + "bap_id": "bap-123", }, }, expectedCode: http.StatusOK, - expectedKeys: []string{"transaction_id", "message_id"}, + expectedKeys: []string{"transaction_id", "message_id", "bap_id"}, + role: "bap", }, { - name: "Valid keys, do not update existing keys", + name: "Valid keys, do not update existing keys with bpp role", config: &Config{ checkKeys: []string{"transaction_id", "message_id"}, + Role: "bpp", }, requestBody: map[string]any{ "context": map[string]any{ "transaction_id": "existing-transaction", "message_id": "existing-message", + "bpp_id": "bpp-456", }, }, expectedCode: http.StatusOK, - expectedKeys: []string{"transaction_id", "message_id"}, + expectedKeys: []string{"transaction_id", "message_id", "bpp_id"}, + role: "bpp", }, { name: "Missing context key", @@ -65,18 +71,6 @@ func TestNewUUIDSetter(t *testing.T) { }, expectedCode: http.StatusBadRequest, }, - { - name: "Empty checkKeys in config", - config: &Config{ - checkKeys: []string{}, - }, - requestBody: map[string]any{ - "context": map[string]any{ - "transaction_id": "", - }, - }, - expectedCode: http.StatusInternalServerError, - }, { name: "Nil config", config: nil, @@ -97,48 +91,44 @@ func TestNewUUIDSetter(t *testing.T) { if err != nil { t.Fatalf("Unexpected error while creating middleware: %v", err) } - - // Prepare request bodyBytes, _ := json.Marshal(tt.requestBody) req := httptest.NewRequest(http.MethodPost, "/test", bytes.NewReader(bodyBytes)) req.Header.Set("Content-Type", "application/json") rec := httptest.NewRecorder() - - // Define a dummy handler dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() w.WriteHeader(http.StatusOK) - if _, err := io.Copy(w, r.Body); err != nil { - http.Error(w, "Failed to copy request body", http.StatusInternalServerError) + if subID, ok := ctx.Value(subscriberIDKey).(string); ok { + response := map[string]any{ + "subscriber_id": subID, + } + json.NewEncoder(w).Encode(response) + } else { + http.Error(w, "Subscriber ID not found", http.StatusInternalServerError) return } }) - - // Apply middleware middleware(dummyHandler).ServeHTTP(rec, req) - - // Check status code if rec.Code != tt.expectedCode { t.Errorf("Expected status code %d, but got %d", tt.expectedCode, rec.Code) } - - // If success, check updated keys if rec.Code == http.StatusOK { var responseBody map[string]any if err := json.Unmarshal(rec.Body.Bytes(), &responseBody); err != nil { t.Fatal("Failed to unmarshal response body:", err) } - - // Validate updated keys - contextData, ok := responseBody[contextKey].(map[string]any) - if !ok { - t.Fatalf("Expected context to be a map, got %T", responseBody[contextKey]) + expectedSubIDKey := "bap_id" + if tt.role == "bpp" { + expectedSubIDKey = "bpp_id" } - for _, key := range tt.expectedKeys { - value, exists := contextData[key] - if !exists || isEmpty(value) { - t.Errorf("Expected key %s to be set, but it's missing or empty", key) + if subID, ok := responseBody["subscriber_id"].(string); ok { + expectedSubID := tt.requestBody["context"].(map[string]any)[expectedSubIDKey] + if subID != expectedSubID { + t.Errorf("Expected subscriber_id %v, but got %v", expectedSubID, subID) } + } else { + t.Error("subscriber_id not found in response") } } }) From d4b2c95df89d2e17de40d22093bc39907eea758d Mon Sep 17 00:00:00 2001 From: "mayur.popli" Date: Fri, 21 Mar 2025 10:16:51 +0530 Subject: [PATCH 06/21] feat: req preprocessor --- .../requestPreProcessor/reqpreprocessor_test.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/shared/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go b/shared/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go index 85001df..6c51307 100644 --- a/shared/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go +++ b/shared/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go @@ -102,8 +102,11 @@ func TestNewUUIDSetter(t *testing.T) { response := map[string]any{ "subscriber_id": subID, } - json.NewEncoder(w).Encode(response) - } else { + if err := json.NewEncoder(w).Encode(response); err != nil { + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + } else { http.Error(w, "Subscriber ID not found", http.StatusInternalServerError) return } From 96c8eec809ed33d5a1eeae0b0f32ecb6d91ff2be Mon Sep 17 00:00:00 2001 From: tanyamadaan Date: Fri, 21 Mar 2025 23:45:08 +0530 Subject: [PATCH 07/21] Changes to plugin manager --- pkg/plugin/manager.go | 44 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/pkg/plugin/manager.go b/pkg/plugin/manager.go index 209e0e6..6b912f8 100644 --- a/pkg/plugin/manager.go +++ b/pkg/plugin/manager.go @@ -19,6 +19,7 @@ type Config struct { Encrypter PluginConfig `yaml:"encrypter"` Publisher PluginConfig `yaml:"publisher"` SchemaValidator PluginConfig `yaml:"schemaValidator"` + Router PluginConfig `yaml:"router"` } // PluginConfig represents configuration details for a plugin. @@ -40,6 +41,7 @@ type Manager struct { ep definition.EncrypterProvider pb definition.PublisherProvider svp definition.SchemaValidatorProvider + rp definition.RouterProvider cfg *Config } @@ -79,7 +81,19 @@ func NewManager(ctx context.Context, cfg *Config) (*Manager, error) { return nil, fmt.Errorf("failed to load encryption plugin: %w", err) } - return &Manager{sp: sp, vp: vp, pb: pb, ep: ep, dp: dp, cfg: cfg}, nil + // Load router plugin. + rp, err := provider[definition.RouterProvider](cfg.Root, cfg.Router.ID) + if err != nil { + return nil, fmt.Errorf("failed to load encryption plugin: %w", err) + } + + // Load schema validator plugin + svp, err := provider[definition.SchemaValidatorProvider](cfg.Root, cfg.SchemaValidator.ID) + if err != nil { + return nil, fmt.Errorf("failed to load validator plugin: %w", err) + } + + return &Manager{sp: sp, vp: vp, pb: pb, ep: ep, dp: dp, rp: rp, svp: svp, cfg: cfg}, nil } // provider loads a plugin dynamically and retrieves its provider instance. @@ -176,3 +190,31 @@ func (m *Manager) Publisher(ctx context.Context) (definition.Publisher, error) { } return publisher, nil } + +// SchemaValidator retrieves the validation plugin instances. +func (m *Manager) SchemaValidator(ctx context.Context) (definition.SchemaValidator, func() error, error) { + if m.svp == nil { + return nil, nil, fmt.Errorf("schema validator plugin provider not loaded") + + } + schemaValidator, close, err := m.svp.New(ctx, m.cfg.SchemaValidator.Config) + if err != nil { + + return nil, nil, fmt.Errorf("failed to initialize schema validator: %v", err) + } + return schemaValidator, close, nil +} + +// Router retrieves the router plugin instances. +func (m *Manager) Router(ctx context.Context) (definition.Router, func() error, error) { + if m.rp == nil { + return nil, nil, fmt.Errorf("router plugin provider not loaded") + + } + schemaValidator, close, err := m.rp.New(ctx, m.cfg.Router.Config) + if err != nil { + + return nil, nil, fmt.Errorf("failed to initialize schema validator: %v", err) + } + return schemaValidator, close, nil +} From a5e0c0ca198429e2286505edeaa812f7765d4521 Mon Sep 17 00:00:00 2001 From: tanyamadaan Date: Fri, 21 Mar 2025 23:48:04 +0530 Subject: [PATCH 08/21] Removed unused files --- pkg/plugin/definition/schemaValidator.go | 16 - .../schemaValidator/cmd/plugin.go | 33 -- .../schemaValidator/cmd/plugin_test.go | 160 -------- .../schemaValidator/schemaValidator.go | 202 ---------- .../schemaValidator/schemaValidator_test.go | 369 ------------------ pkg/plugin/manager.go | 43 +- 6 files changed, 8 insertions(+), 815 deletions(-) delete mode 100644 pkg/plugin/definition/schemaValidator.go delete mode 100644 pkg/plugin/implementation/schemaValidator/cmd/plugin.go delete mode 100644 pkg/plugin/implementation/schemaValidator/cmd/plugin_test.go delete mode 100644 pkg/plugin/implementation/schemaValidator/schemaValidator.go delete mode 100644 pkg/plugin/implementation/schemaValidator/schemaValidator_test.go diff --git a/pkg/plugin/definition/schemaValidator.go b/pkg/plugin/definition/schemaValidator.go deleted file mode 100644 index dc69ca5..0000000 --- a/pkg/plugin/definition/schemaValidator.go +++ /dev/null @@ -1,16 +0,0 @@ -package definition - -import ( - "context" - "net/url" -) - -// SchemaValidator interface for schema validation. -type SchemaValidator interface { - Validate(ctx context.Context, url *url.URL, payload []byte) error -} - -// SchemaValidatorProvider interface for creating validators. -type SchemaValidatorProvider interface { - New(ctx context.Context, config map[string]string) (SchemaValidator, func() error, error) -} diff --git a/pkg/plugin/implementation/schemaValidator/cmd/plugin.go b/pkg/plugin/implementation/schemaValidator/cmd/plugin.go deleted file mode 100644 index 6882ce4..0000000 --- a/pkg/plugin/implementation/schemaValidator/cmd/plugin.go +++ /dev/null @@ -1,33 +0,0 @@ -package main - -import ( - "context" - "errors" - - definition "github.com/beckn/beckn-onix/pkg/plugin/definition" - schemaValidator "github.com/beckn/beckn-onix/pkg/plugin/implementation/schemaValidator" -) - -// schemaValidatorProvider provides instances of schemaValidator. -type schemaValidatorProvider struct{} - -// New initializes a new Verifier instance. -func (vp schemaValidatorProvider) New(ctx context.Context, config map[string]string) (definition.SchemaValidator, func() error, error) { - if ctx == nil { - return nil, nil, errors.New("context cannot be nil") - } - - // Extract schema_dir from the config map - schemaDir, ok := config["schema_dir"] - if !ok || schemaDir == "" { - return nil, nil, errors.New("config must contain 'schema_dir'") - } - - // Create a new schemaValidator instance with the provided configuration - return schemaValidator.New(ctx, &schemaValidator.Config{ - SchemaDir: schemaDir, - }) -} - -// Provider is the exported symbol that the plugin manager will look for. -var Provider definition.SchemaValidatorProvider = schemaValidatorProvider{} diff --git a/pkg/plugin/implementation/schemaValidator/cmd/plugin_test.go b/pkg/plugin/implementation/schemaValidator/cmd/plugin_test.go deleted file mode 100644 index 7e06b55..0000000 --- a/pkg/plugin/implementation/schemaValidator/cmd/plugin_test.go +++ /dev/null @@ -1,160 +0,0 @@ -package main - -import ( - "context" - "os" - "path/filepath" - "strings" - "testing" -) - -// setupTestSchema creates a temporary directory and writes a sample schema file. -func setupTestSchema(t *testing.T) string { - t.Helper() - - // Create a temporary directory for the schema - schemaDir, err := os.MkdirTemp("", "schemas") - if err != nil { - t.Fatalf("Failed to create temp directory: %v", err) - } - - // Create the directory structure for the schema file - schemaFilePath := filepath.Join(schemaDir, "example", "1.0", "test_schema.json") - if err := os.MkdirAll(filepath.Dir(schemaFilePath), 0755); err != nil { - t.Fatalf("Failed to create schema directory structure: %v", err) - } - - // Define a sample schema - schemaContent := `{ - "type": "object", - "properties": { - "context": { - "type": "object", - "properties": { - "domain": {"type": "string"}, - "version": {"type": "string"} - }, - "required": ["domain", "version"] - } - }, - "required": ["context"] - }` - - // Write the schema to the file - if err := os.WriteFile(schemaFilePath, []byte(schemaContent), 0644); err != nil { - t.Fatalf("Failed to write schema file: %v", err) - } - - return schemaDir -} - -// TestValidatorProviderSuccess tests successful ValidatorProvider implementation. -func TestValidatorProviderSuccess(t *testing.T) { - schemaDir := setupTestSchema(t) - defer os.RemoveAll(schemaDir) - - // Define test cases. - tests := []struct { - name string - ctx context.Context - config map[string]string - expectedError string - }{ - { - name: "Valid schema directory", - ctx: context.Background(), // Valid context - config: map[string]string{"schema_dir": schemaDir}, - expectedError: "", - }, - } - - // Test using table-driven tests - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - vp := schemaValidatorProvider{} - schemaValidator, close, err := vp.New(tt.ctx, tt.config) - - // Ensure no error occurred - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - - // Ensure the schemaValidator is not nil - if schemaValidator == nil { - t.Error("expected a non-nil schemaValidator, got nil") - } - - // Ensure the close function is not nil - if close == nil { - t.Error("expected a non-nil close function, got nil") - } - - // Test the close function - if err := close(); err != nil { - t.Errorf("close function returned an error: %v", err) - } - }) - } -} - -// TestValidatorProviderSuccess tests cases where ValidatorProvider creation should fail. -func TestValidatorProviderFailure(t *testing.T) { - schemaDir := setupTestSchema(t) - defer os.RemoveAll(schemaDir) - - // Define test cases. - tests := []struct { - name string - ctx context.Context - config map[string]string - expectedError string - }{ - { - name: "Config is empty", - ctx: context.Background(), - config: map[string]string{}, - expectedError: "config must contain 'schema_dir'", - }, - { - name: "schema_dir is empty", - ctx: context.Background(), - config: map[string]string{"schema_dir": ""}, - expectedError: "config must contain 'schema_dir'", - }, - { - name: "Invalid schema directory", - ctx: context.Background(), // Valid context - config: map[string]string{"schema_dir": "/invalid/dir"}, - expectedError: "failed to initialise schemaValidator: schema directory does not exist: /invalid/dir", - }, - { - name: "Nil context", - ctx: nil, // Nil context - config: map[string]string{"schema_dir": schemaDir}, - expectedError: "context cannot be nil", - }, - } - - // Test using table-driven tests - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - vp := schemaValidatorProvider{} - _, _, err := vp.New(tt.ctx, tt.config) - - // 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) - return - } - }) - } -} diff --git a/pkg/plugin/implementation/schemaValidator/schemaValidator.go b/pkg/plugin/implementation/schemaValidator/schemaValidator.go deleted file mode 100644 index a46ceb2..0000000 --- a/pkg/plugin/implementation/schemaValidator/schemaValidator.go +++ /dev/null @@ -1,202 +0,0 @@ -package schemaValidator - -import ( - "context" - "encoding/json" - "fmt" - "net/url" - "os" - "path" - "path/filepath" - "strings" - - response "github.com/beckn/beckn-onix/pkg/response" - - "github.com/santhosh-tekuri/jsonschema/v6" -) - -// Payload represents the structure of the data payload with context information. -type payload struct { - Context struct { - Domain string `json:"domain"` - Version string `json:"version"` - } `json:"context"` -} - -// SchemaValidator implements the Validator interface. -type SchemaValidator struct { - config *Config - schemaCache map[string]*jsonschema.Schema -} - -// Config struct for SchemaValidator. -type Config struct { - SchemaDir string -} - -// New creates a new ValidatorProvider instance. -func New(ctx context.Context, config *Config) (*SchemaValidator, func() error, error) { - // Check if config is nil - if config == nil { - return nil, nil, fmt.Errorf("config cannot be nil") - } - v := &SchemaValidator{ - config: config, - schemaCache: make(map[string]*jsonschema.Schema), - } - - // Call Initialise function to load schemas and get validators - if err := v.initialise(); err != nil { - return nil, nil, fmt.Errorf("failed to initialise schemaValidator: %v", err) - } - return v, v.Close, nil -} - -// Validate validates the given data against the schema. -func (v *SchemaValidator) Validate(ctx context.Context, url *url.URL, data []byte) error { - var payloadData payload - err := json.Unmarshal(data, &payloadData) - if err != nil { - return fmt.Errorf("failed to parse JSON payload: %v", err) - } - - // Extract domain, version, and endpoint from the payload and uri. - cxt_domain := payloadData.Context.Domain - version := payloadData.Context.Version - version = fmt.Sprintf("v%s", version) - - endpoint := path.Base(url.String()) - // ToDo Add debug log here - fmt.Println("Handling request for endpoint:", endpoint) - domain := strings.ToLower(cxt_domain) - domain = strings.ReplaceAll(domain, ":", "_") - - // Construct the schema file name. - schemaFileName := fmt.Sprintf("%s_%s_%s", domain, version, endpoint) - - // Retrieve the schema from the cache. - schema, exists := v.schemaCache[schemaFileName] - if !exists { - return fmt.Errorf("schema not found for domain: %s", schemaFileName) - } - - var jsonData any - if err := json.Unmarshal(data, &jsonData); err != nil { - return fmt.Errorf("failed to parse JSON data: %v", err) - } - err = schema.Validate(jsonData) - if err != nil { - // Handle schema validation errors - if validationErr, ok := err.(*jsonschema.ValidationError); ok { - // Convert validation errors into an array of SchemaValError - var schemaErrors []response.Error - for _, cause := range validationErr.Causes { - // Extract the path and message from the validation error - path := strings.Join(cause.InstanceLocation, ".") // JSON path to the invalid field - message := cause.Error() // Validation error message - - // Append the error to the schemaErrors array - schemaErrors = append(schemaErrors, response.Error{ - Paths: path, - Message: message, - }) - } - // Return the array of schema validation errors - return &response.SchemaValidationErr{Errors: schemaErrors} - } - // Return a generic error for non-validation errors - return fmt.Errorf("validation failed: %v", err) - } - - // Return nil if validation succeeds - return nil -} - -// ValidatorProvider provides instances of Validator. -type ValidatorProvider struct{} - -// Initialise initialises the validator provider by compiling all the JSON schema files -// from the specified directory and storing them in a cache indexed by their schema filenames. -func (v *SchemaValidator) initialise() error { - schemaDir := v.config.SchemaDir - // Check if the directory exists and is accessible. - info, err := os.Stat(schemaDir) - if err != nil { - if os.IsNotExist(err) { - return fmt.Errorf("schema directory does not exist: %s", schemaDir) - } - return fmt.Errorf("failed to access schema directory: %v", err) - } - if !info.IsDir() { - return fmt.Errorf("provided schema path is not a directory: %s", schemaDir) - } - - compiler := jsonschema.NewCompiler() - - // Helper function to process directories recursively. - var processDir func(dir string) error - processDir = func(dir string) error { - entries, err := os.ReadDir(dir) - if err != nil { - return fmt.Errorf("failed to read directory: %v", err) - } - - for _, entry := range entries { - path := filepath.Join(dir, entry.Name()) - if entry.IsDir() { - // Recursively process subdirectories. - if err := processDir(path); err != nil { - return err - } - } else if filepath.Ext(entry.Name()) == ".json" { - // Process JSON files. - compiledSchema, err := compiler.Compile(path) - if err != nil { - return fmt.Errorf("failed to compile JSON schema from file %s: %v", entry.Name(), err) - } - - // Use relative path from schemaDir to avoid absolute paths and make schema keys domain/version specific. - relativePath, err := filepath.Rel(schemaDir, path) - if err != nil { - return fmt.Errorf("failed to get relative path for file %s: %v", entry.Name(), err) - } - // Split the relative path to get domain, version, and schema. - parts := strings.Split(relativePath, string(os.PathSeparator)) - - // Ensure that the file path has at least 3 parts: domain, version, and schema file. - if len(parts) < 3 { - return fmt.Errorf("invalid schema file structure, expected domain/version/schema.json but got: %s", relativePath) - } - - // Extract domain, version, and schema filename from the parts. - // Validate that the extracted parts are non-empty. - domain := strings.TrimSpace(parts[0]) - version := strings.TrimSpace(parts[1]) - schemaFileName := strings.TrimSpace(parts[2]) - schemaFileName = strings.TrimSuffix(schemaFileName, ".json") - - if domain == "" || version == "" || schemaFileName == "" { - return fmt.Errorf("invalid schema file structure, one or more components are empty. Relative path: %s", relativePath) - } - - // Construct a unique key combining domain, version, and schema name (e.g., ondc_trv10_v2.0.0_schema). - uniqueKey := fmt.Sprintf("%s_%s_%s", domain, version, schemaFileName) - // Store the compiled schema in the SchemaCache using the unique key. - v.schemaCache[uniqueKey] = compiledSchema - } - } - return nil - } - - // Start processing from the root schema directory. - if err := processDir(schemaDir); err != nil { - return fmt.Errorf("failed to read schema directory: %v", err) - } - - return nil -} - -// Close releases resources (mock implementation returning nil). -func (v *SchemaValidator) Close() error { - return nil -} diff --git a/pkg/plugin/implementation/schemaValidator/schemaValidator_test.go b/pkg/plugin/implementation/schemaValidator/schemaValidator_test.go deleted file mode 100644 index b60c834..0000000 --- a/pkg/plugin/implementation/schemaValidator/schemaValidator_test.go +++ /dev/null @@ -1,369 +0,0 @@ -package schemaValidator - -import ( - "context" - "net/url" - "os" - "path/filepath" - "strings" - "testing" - - "github.com/santhosh-tekuri/jsonschema/v6" -) - -// setupTestSchema creates a temporary directory and writes a sample schema file. -func setupTestSchema(t *testing.T) string { - t.Helper() - - // Create a temporary directory for the schema - schemaDir, err := os.MkdirTemp("", "schemas") - if err != nil { - t.Fatalf("Failed to create temp directory: %v", err) - } - - // Create the directory structure for the schema file - schemaFilePath := filepath.Join(schemaDir, "example", "v1.0", "endpoint.json") - if err := os.MkdirAll(filepath.Dir(schemaFilePath), 0755); err != nil { - t.Fatalf("Failed to create schema directory structure: %v", err) - } - - // Define a sample schema - schemaContent := `{ - "type": "object", - "properties": { - "context": { - "type": "object", - "properties": { - "domain": {"type": "string"}, - "version": {"type": "string"}, - "action": {"type": "string"} - }, - "required": ["domain", "version", "action"] - } - }, - "required": ["context"] - }` - - // Write the schema to the file - if err := os.WriteFile(schemaFilePath, []byte(schemaContent), 0644); err != nil { - t.Fatalf("Failed to write schema file: %v", err) - } - - return schemaDir -} - -func TestValidator_Validate_Success(t *testing.T) { - tests := []struct { - name string - url string - payload string - wantErr bool - }{ - { - name: "Valid payload", - url: "http://example.com/endpoint", - payload: `{"context": {"domain": "example", "version": "1.0", "action": "endpoint"}}`, - wantErr: false, - }, - } - - // Setup a temporary schema directory for testing - schemaDir := setupTestSchema(t) - defer os.RemoveAll(schemaDir) - - config := &Config{SchemaDir: schemaDir} - v, _, err := New(context.Background(), config) - if err != nil { - t.Fatalf("Failed to create validator: %v", err) - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - u, _ := url.Parse(tt.url) - err := v.Validate(context.Background(), u, []byte(tt.payload)) - if err != nil { - t.Errorf("Unexpected error: %v", err) - } else { - t.Logf("Test %s passed with no errors", tt.name) - } - }) - } -} - -func TestValidator_Validate_Failure(t *testing.T) { - tests := []struct { - name string - url string - payload string - wantErr string - }{ - { - name: "Invalid JSON payload", - url: "http://example.com/endpoint", - payload: `{"context": {"domain": "example", "version": "1.0"`, - wantErr: "failed to parse JSON payload", - }, - { - name: "Schema validation failure", - url: "http://example.com/endpoint", - payload: `{"context": {"domain": "example", "version": "1.0"}}`, - wantErr: "context: at '/context': missing property 'action'", - }, - { - name: "Schema not found", - url: "http://example.com/unknown_endpoint", - payload: `{"context": {"domain": "example", "version": "1.0"}}`, - wantErr: "schema not found for domain", - }, - } - - // Setup a temporary schema directory for testing - schemaDir := setupTestSchema(t) - defer os.RemoveAll(schemaDir) - - config := &Config{SchemaDir: schemaDir} - v, _, err := New(context.Background(), config) - if err != nil { - t.Fatalf("Failed to create validator: %v", err) - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - u, _ := url.Parse(tt.url) - err := v.Validate(context.Background(), u, []byte(tt.payload)) - if tt.wantErr != "" { - if err == nil { - t.Errorf("Expected error containing '%s', but got nil", tt.wantErr) - } else if !strings.Contains(err.Error(), tt.wantErr) { - t.Errorf("Expected error containing '%s', but got '%v'", tt.wantErr, err) - } else { - t.Logf("Test %s passed with expected error: %v", tt.name, err) - } - } else { - if err != nil { - t.Errorf("Unexpected error: %v", err) - } else { - t.Logf("Test %s passed with no errors", tt.name) - } - } - }) - } -} - -func TestValidator_Initialise(t *testing.T) { - tests := []struct { - name string - setupFunc func(schemaDir string) error - wantErr string - }{ - { - name: "Schema directory does not exist", - setupFunc: func(schemaDir string) error { - // Do not create the schema directory - return nil - - }, - wantErr: "schema directory does not exist", - }, - { - name: "Schema path is not a directory", - setupFunc: func(schemaDir string) error { - // Create a file instead of a directory - return os.WriteFile(schemaDir, []byte{}, 0644) - }, - wantErr: "provided schema path is not a directory", - }, - { - name: "Invalid schema file structure", - setupFunc: func(schemaDir string) error { - // Create an invalid schema file structure - invalidSchemaFile := filepath.Join(schemaDir, "invalid_schema.json") - if err := os.MkdirAll(filepath.Dir(invalidSchemaFile), 0755); err != nil { - t.Fatalf("Failed to create directory: %v", err) - } - return os.WriteFile(invalidSchemaFile, []byte(`{}`), 0644) - }, - wantErr: "invalid schema file structure", - }, - { - name: "Failed to compile JSON schema", - setupFunc: func(schemaDir string) error { - // Create a schema file with invalid JSON - invalidSchemaFile := filepath.Join(schemaDir, "example", "1.0", "endpoint.json") - if err := os.MkdirAll(filepath.Dir(invalidSchemaFile), 0755); err != nil { - t.Fatalf("Failed to create directory: %v", err) - } - return os.WriteFile(invalidSchemaFile, []byte(`{invalid json}`), 0644) - }, - wantErr: "failed to compile JSON schema", - }, - { - name: "Invalid schema file structure with empty components", - setupFunc: func(schemaDir string) error { - // Create a schema file with empty domain, version, or schema name - invalidSchemaFile := filepath.Join(schemaDir, "", "1.0", "endpoint.json") - if err := os.MkdirAll(filepath.Dir(invalidSchemaFile), 0755); err != nil { - t.Fatalf("Failed to create directory: %v", err) - } - return os.WriteFile(invalidSchemaFile, []byte(`{ - "type": "object", - "properties": { - "context": { - "type": "object", - "properties": { - "domain": {"type": "string"}, - "version": {"type": "string"} - }, - "required": ["domain", "version"] - } - }, - "required": ["context"] - }`), 0644) - }, - wantErr: "failed to read schema directory: invalid schema file structure, expected domain/version/schema.json but got: 1.0/endpoint.json", - }, - { - name: "Failed to read directory", - setupFunc: func(schemaDir string) error { - // Create a directory and remove read permissions - if err := os.MkdirAll(schemaDir, 0000); err != nil { - t.Fatalf("Failed to create directory: %v", err) - } - return nil - }, - wantErr: "failed to read directory", - }, - { - name: "Valid schema directory", - setupFunc: func(schemaDir string) error { - // Create a valid schema file - validSchemaFile := filepath.Join(schemaDir, "example", "1.0", "endpoint.json") - if err := os.MkdirAll(filepath.Dir(validSchemaFile), 0755); err != nil { - t.Fatalf("Failed to create directory: %v", err) - } - return os.WriteFile(validSchemaFile, []byte(`{ - "type": "object", - "properties": { - "context": { - "type": "object", - "properties": { - "domain": {"type": "string"}, - "version": {"type": "string"} - }, - "required": ["domain", "version"] - } - }, - "required": ["context"] - }`), 0644) - }, - wantErr: "", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Setup a temporary schema directory for testing - schemaDir := filepath.Join(os.TempDir(), "schemas") - defer os.RemoveAll(schemaDir) - - // Run the setup function to prepare the test case - if err := tt.setupFunc(schemaDir); err != nil { - t.Fatalf("setupFunc() error = %v", err) - } - - config := &Config{SchemaDir: schemaDir} - v := &SchemaValidator{ - config: config, - schemaCache: make(map[string]*jsonschema.Schema), - } - - err := v.initialise() - if (err != nil && !strings.Contains(err.Error(), tt.wantErr)) || (err == nil && tt.wantErr != "") { - t.Errorf("Error: initialise() returned error = %v, expected error = %v", err, tt.wantErr) - } else if err == nil { - t.Logf("Test %s passed: validator initialized successfully", tt.name) - } else { - t.Logf("Test %s passed with expected error: %v", tt.name, err) - } - }) - } -} - -func TestValidator_New_Success(t *testing.T) { - schemaDir := setupTestSchema(t) - defer os.RemoveAll(schemaDir) - - config := &Config{SchemaDir: schemaDir} - _, _, err := New(context.Background(), config) - if err != nil { - t.Errorf("Unexpected error: %v", err) - } -} - -func TestValidator_New_Failure(t *testing.T) { - tests := []struct { - name string - config *Config - setupFunc func(schemaDir string) error - wantErr string - }{ - { - name: "Config is nil", - config: nil, - setupFunc: func(schemaDir string) error { - return nil - }, - wantErr: "config cannot be nil", - }, - // { - // name: "Config is empty", - // config: &Config{}, - // setupFunc: func(schemaDir string) error { - // return nil - // }, - // wantErr: "config must contain 'schema_dir'", - // }, - // { - // name: "schema_dir is empty", - // config: &Config{SchemaDir: ""}, - // setupFunc: func(schemaDir string) error { - // return nil - // }, - // wantErr: "config must contain 'schema_dir'", - // }, - { - name: "Failed to initialise validators", - config: &Config{ - SchemaDir: "/invalid/path", - }, - setupFunc: func(schemaDir string) error { - // Do not create the schema directory - return nil - }, - wantErr: "ailed to initialise schemaValidator: schema directory does not exist: /invalid/path", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // Run the setup function if provided - if tt.setupFunc != nil { - schemaDir := "" - if tt.config != nil { - schemaDir = tt.config.SchemaDir - } - if err := tt.setupFunc(schemaDir); err != nil { - t.Fatalf("Setup function failed: %v", err) - } - } - - // Call the New function with the test config - _, _, err := New(context.Background(), tt.config) - if (err != nil && !strings.Contains(err.Error(), tt.wantErr)) || (err == nil && tt.wantErr != "") { - t.Errorf("Error: New() returned error = %v, expected error = %v", err, tt.wantErr) - } else { - t.Logf("Test %s passed with expected error: %v", tt.name, err) - } - }) - } -} diff --git a/pkg/plugin/manager.go b/pkg/plugin/manager.go index 6b912f8..6a4cecd 100644 --- a/pkg/plugin/manager.go +++ b/pkg/plugin/manager.go @@ -12,14 +12,13 @@ import ( // Config represents the plugin manager configuration. type Config struct { - Root string `yaml:"root"` - Signer PluginConfig `yaml:"signer"` - Verifier PluginConfig `yaml:"verifier"` - Decrypter PluginConfig `yaml:"decrypter"` - Encrypter PluginConfig `yaml:"encrypter"` - Publisher PluginConfig `yaml:"publisher"` - SchemaValidator PluginConfig `yaml:"schemaValidator"` - Router PluginConfig `yaml:"router"` + Root string `yaml:"root"` + Signer PluginConfig `yaml:"signer"` + Verifier PluginConfig `yaml:"verifier"` + Decrypter PluginConfig `yaml:"decrypter"` + Encrypter PluginConfig `yaml:"encrypter"` + Publisher PluginConfig `yaml:"publisher"` + Router PluginConfig `yaml:"router"` } // PluginConfig represents configuration details for a plugin. @@ -28,11 +27,6 @@ type PluginConfig struct { Config map[string]string `yaml:"config"` } -// SchemaDetails contains information about the plugin schema directory. -type SchemaDetails struct { - SchemaDir string `yaml:"schemaDir"` -} - // Manager handles dynamic plugin loading and management. type Manager struct { sp definition.SignerProvider @@ -40,7 +34,6 @@ type Manager struct { dp definition.DecrypterProvider ep definition.EncrypterProvider pb definition.PublisherProvider - svp definition.SchemaValidatorProvider rp definition.RouterProvider cfg *Config } @@ -87,13 +80,7 @@ func NewManager(ctx context.Context, cfg *Config) (*Manager, error) { return nil, fmt.Errorf("failed to load encryption plugin: %w", err) } - // Load schema validator plugin - svp, err := provider[definition.SchemaValidatorProvider](cfg.Root, cfg.SchemaValidator.ID) - if err != nil { - return nil, fmt.Errorf("failed to load validator plugin: %w", err) - } - - return &Manager{sp: sp, vp: vp, pb: pb, ep: ep, dp: dp, rp: rp, svp: svp, cfg: cfg}, nil + return &Manager{sp: sp, vp: vp, pb: pb, ep: ep, dp: dp, rp: rp, cfg: cfg}, nil } // provider loads a plugin dynamically and retrieves its provider instance. @@ -191,20 +178,6 @@ func (m *Manager) Publisher(ctx context.Context) (definition.Publisher, error) { return publisher, nil } -// SchemaValidator retrieves the validation plugin instances. -func (m *Manager) SchemaValidator(ctx context.Context) (definition.SchemaValidator, func() error, error) { - if m.svp == nil { - return nil, nil, fmt.Errorf("schema validator plugin provider not loaded") - - } - schemaValidator, close, err := m.svp.New(ctx, m.cfg.SchemaValidator.Config) - if err != nil { - - return nil, nil, fmt.Errorf("failed to initialize schema validator: %v", err) - } - return schemaValidator, close, nil -} - // Router retrieves the router plugin instances. func (m *Manager) Router(ctx context.Context) (definition.Router, func() error, error) { if m.rp == nil { From 97db5cf6e7d760bdbb49cabee899a1dcd9709979 Mon Sep 17 00:00:00 2001 From: "mayur.popli" Date: Mon, 24 Mar 2025 02:35:25 +0530 Subject: [PATCH 09/21] fix: reqpreprocessor --- .../requestPreProcessor/reqpreprocessor.go | 107 -------------- .../reqpreprocessor_test.go | 139 ------------------ 2 files changed, 246 deletions(-) delete mode 100644 shared/plugin/implementation/requestPreProcessor/reqpreprocessor.go delete mode 100644 shared/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go diff --git a/shared/plugin/implementation/requestPreProcessor/reqpreprocessor.go b/shared/plugin/implementation/requestPreProcessor/reqpreprocessor.go deleted file mode 100644 index f5964c2..0000000 --- a/shared/plugin/implementation/requestPreProcessor/reqpreprocessor.go +++ /dev/null @@ -1,107 +0,0 @@ -package reqpreprocessor - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - - "github.com/google/uuid" -) - -type Config struct { - checkKeys []string - Role string -} -type contextKeyType string - -const contextKey = "context" -const subscriberIDKey contextKeyType = "subscriber_id" - -func NewUUIDSetter(cfg *Config) (func(http.Handler) http.Handler, error) { - if err := validateConfig(cfg); err != nil { - return nil, err - } - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - - var data map[string]any - body, err := io.ReadAll(r.Body) - if err != nil { - http.Error(w, "Failed to read request body", http.StatusInternalServerError) - return - } - if err := json.Unmarshal(body, &data); err != nil { - http.Error(w, "Failed to decode request body", http.StatusBadRequest) - return - } - contextRaw := data[contextKey] - if contextRaw == nil { - http.Error(w, fmt.Sprintf("%s field not found.", contextKey), http.StatusBadRequest) - return - } - contextData, ok := contextRaw.(map[string]any) - if !ok { - http.Error(w, fmt.Sprintf("%s field is not a map.", contextKey), http.StatusBadRequest) - return - } - var subID any - switch cfg.Role { - case "bap": - subID = contextData["bap_id"] - case "bpp": - subID = contextData["bpp_id"] - } - ctx := context.WithValue(r.Context(), subscriberIDKey, subID) - for _, key := range cfg.checkKeys { - value := uuid.NewString() - updatedValue := update(contextData, key, value) - ctx = context.WithValue(ctx, contextKeyType(key), updatedValue) - } - data[contextKey] = contextData - updatedBody, err := json.Marshal(data) - if err != nil { - http.Error(w, "Failed to marshal updated JSON", http.StatusInternalServerError) - return - } - r.Body = io.NopCloser(bytes.NewBuffer(updatedBody)) - r.ContentLength = int64(len(updatedBody)) - r = r.WithContext(ctx) - next.ServeHTTP(w, r) - }) - }, nil -} - -func update(wrapper map[string]any, key string, value any) any { - field, exists := wrapper[key] - if !exists || isEmpty(field) { - wrapper[key] = value - return value - } - return field -} -func isEmpty(v any) bool { - switch v := v.(type) { - case string: - return v == "" - case nil: - return true - default: - return false - } -} - -func validateConfig(cfg *Config) error { - if cfg == nil { - return errors.New("config cannot be nil") - } - for _, key := range cfg.checkKeys { - if key == "" { - return errors.New("checkKeys cannot contain empty strings") - } - } - return nil -} diff --git a/shared/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go b/shared/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go deleted file mode 100644 index 6c51307..0000000 --- a/shared/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go +++ /dev/null @@ -1,139 +0,0 @@ -package reqpreprocessor - -import ( - "bytes" - "encoding/json" - "net/http" - "net/http/httptest" - "testing" -) - -func TestNewUUIDSetter(t *testing.T) { - tests := []struct { - name string - config *Config - requestBody map[string]any - expectedCode int - expectedKeys []string - role string - }{ - { - name: "Valid keys, update missing keys with bap role", - config: &Config{ - checkKeys: []string{"transaction_id", "message_id"}, - Role: "bap", - }, - requestBody: map[string]any{ - "context": map[string]any{ - "transaction_id": "", - "message_id": nil, - "bap_id": "bap-123", - }, - }, - expectedCode: http.StatusOK, - expectedKeys: []string{"transaction_id", "message_id", "bap_id"}, - role: "bap", - }, - { - name: "Valid keys, do not update existing keys with bpp role", - config: &Config{ - checkKeys: []string{"transaction_id", "message_id"}, - Role: "bpp", - }, - requestBody: map[string]any{ - "context": map[string]any{ - "transaction_id": "existing-transaction", - "message_id": "existing-message", - "bpp_id": "bpp-456", - }, - }, - expectedCode: http.StatusOK, - expectedKeys: []string{"transaction_id", "message_id", "bpp_id"}, - role: "bpp", - }, - { - name: "Missing context key", - config: &Config{ - checkKeys: []string{"transaction_id"}, - }, - requestBody: map[string]any{ - "otherKey": "value", - }, - expectedCode: http.StatusBadRequest, - }, - { - name: "Invalid context type", - config: &Config{ - checkKeys: []string{"transaction_id"}, - }, - requestBody: map[string]any{ - "context": "not-a-map", - }, - expectedCode: http.StatusBadRequest, - }, - { - name: "Nil config", - config: nil, - requestBody: map[string]any{}, - expectedCode: http.StatusInternalServerError, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - middleware, err := NewUUIDSetter(tt.config) - if tt.config == nil || len(tt.config.checkKeys) == 0 { - if err == nil { - t.Fatal("Expected an error, but got none") - } - return - } - if err != nil { - t.Fatalf("Unexpected error while creating middleware: %v", err) - } - bodyBytes, _ := json.Marshal(tt.requestBody) - req := httptest.NewRequest(http.MethodPost, "/test", bytes.NewReader(bodyBytes)) - req.Header.Set("Content-Type", "application/json") - rec := httptest.NewRecorder() - dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - w.WriteHeader(http.StatusOK) - if subID, ok := ctx.Value(subscriberIDKey).(string); ok { - response := map[string]any{ - "subscriber_id": subID, - } - if err := json.NewEncoder(w).Encode(response); err != nil { - http.Error(w, "Internal Server Error", http.StatusInternalServerError) - return - } - } else { - http.Error(w, "Subscriber ID not found", http.StatusInternalServerError) - return - } - }) - middleware(dummyHandler).ServeHTTP(rec, req) - if rec.Code != tt.expectedCode { - t.Errorf("Expected status code %d, but got %d", tt.expectedCode, rec.Code) - } - if rec.Code == http.StatusOK { - var responseBody map[string]any - if err := json.Unmarshal(rec.Body.Bytes(), &responseBody); err != nil { - t.Fatal("Failed to unmarshal response body:", err) - } - expectedSubIDKey := "bap_id" - if tt.role == "bpp" { - expectedSubIDKey = "bpp_id" - } - - if subID, ok := responseBody["subscriber_id"].(string); ok { - expectedSubID := tt.requestBody["context"].(map[string]any)[expectedSubIDKey] - if subID != expectedSubID { - t.Errorf("Expected subscriber_id %v, but got %v", expectedSubID, subID) - } - } else { - t.Error("subscriber_id not found in response") - } - } - }) - } -} From 81a82df6f91982dcefbebee19ac23be4f8b1b40c Mon Sep 17 00:00:00 2001 From: "mayur.popli" Date: Mon, 24 Mar 2025 09:24:14 +0530 Subject: [PATCH 10/21] fix: reqstruct --- .../requestPreProcessor/reqpreprocessor.go | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor.go b/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor.go index 427393e..58a09cc 100644 --- a/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor.go +++ b/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor.go @@ -17,6 +17,10 @@ type Config struct { Role string } +type BecknRequest struct { + Context map[string]any `json:"context"` +} + type contextKeyType string const contextKey = "context" @@ -28,42 +32,35 @@ func NewUUIDSetter(cfg *Config) (func(http.Handler) http.Handler, error) { } return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - - var data map[string]any body, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "Failed to read request body", http.StatusInternalServerError) return } - if err := json.Unmarshal(body, &data); err != nil { + var req BecknRequest + if err := json.Unmarshal(body, &req); err != nil { http.Error(w, "Failed to decode request body", http.StatusBadRequest) return } - contextRaw := data[contextKey] - if contextRaw == nil { + if req.Context == nil { http.Error(w, fmt.Sprintf("%s field not found.", contextKey), http.StatusBadRequest) return } - contextData, ok := contextRaw.(map[string]any) - if !ok { - http.Error(w, fmt.Sprintf("%s field is not a map.", contextKey), http.StatusBadRequest) - return - } var subID any switch cfg.Role { case "bap": - subID = contextData["bap_id"] + subID = req.Context["bap_id"] case "bpp": - subID = contextData["bpp_id"] + subID = req.Context["bpp_id"] } ctx := context.WithValue(r.Context(), subscriberIDKey, subID) for _, key := range cfg.CheckKeys { value := uuid.NewString() - updatedValue := update(contextData, key, value) + updatedValue := update(req.Context, key, value) ctx = context.WithValue(ctx, contextKeyType(key), updatedValue) } - data[contextKey] = contextData - updatedBody, err := json.Marshal(data) + reqData := map[string]any{"context": req.Context} + updatedBody, err := json.Marshal(reqData) if err != nil { http.Error(w, "Failed to marshal updated JSON", http.StatusInternalServerError) return From 77ff19438699d98c55f4162ab437fda43559d3ff Mon Sep 17 00:00:00 2001 From: "mayur.popli" Date: Wed, 26 Mar 2025 12:05:58 +0530 Subject: [PATCH 11/21] fix: resolved comments --- go.mod | 11 ++++++++--- go.sum | 9 +++++++-- .../requestPreProcessor/reqpreprocessor.go | 4 ++-- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index d557016..d4c311e 100644 --- a/go.mod +++ b/go.mod @@ -6,10 +6,15 @@ toolchain go1.23.7 require golang.org/x/crypto v0.36.0 -require github.com/google/uuid v1.6.0 // indirect +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/testify v1.10.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) require ( - github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03 cloud.google.com/go v0.119.0 // indirect cloud.google.com/go/auth v0.15.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect @@ -19,9 +24,9 @@ require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/google/s2a-go v0.1.9 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.5 // indirect github.com/googleapis/gax-go/v2 v2.14.1 // indirect + github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03 go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect diff --git a/go.sum b/go.sum index 2bc3958..838a9ff 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03 h1:m1h+vudopHsI67FPT9MOncyndWhTcdUoBtI1R1uajGY= -github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03/go.mod h1:8sheVFH84v3PCyFY/O02mIgSQY9I6wMYPWsq7mDnEZY= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.119.0 h1:tw7OjErMzJKbbjaEHkrt60KQrK5Wus/boCZ7tm5/RNE= cloud.google.com/go v0.119.0/go.mod h1:fwB8QLzTcNevxqi8dcpR+hoMIs3jBherGS9VUBDAW08= @@ -18,6 +16,7 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -57,6 +56,7 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.5 h1:VgzTY2jogw3xt39CusE github.com/googleapis/enterprise-certificate-proxy v0.3.5/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -65,6 +65,10 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03 h1:m1h+vudopHsI67FPT9MOncyndWhTcdUoBtI1R1uajGY= +github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03/go.mod h1:8sheVFH84v3PCyFY/O02mIgSQY9I6wMYPWsq7mDnEZY= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= @@ -157,6 +161,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor.go b/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor.go index 58a09cc..009fe5b 100644 --- a/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor.go +++ b/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor.go @@ -17,7 +17,7 @@ type Config struct { Role string } -type BecknRequest struct { +type becknRequest struct { Context map[string]any `json:"context"` } @@ -37,7 +37,7 @@ func NewUUIDSetter(cfg *Config) (func(http.Handler) http.Handler, error) { http.Error(w, "Failed to read request body", http.StatusInternalServerError) return } - var req BecknRequest + var req becknRequest if err := json.Unmarshal(body, &req); err != nil { http.Error(w, "Failed to decode request body", http.StatusBadRequest) return From 1c9a7ae5950d59782d2b7ef2a721a953a620336c Mon Sep 17 00:00:00 2001 From: "mayur.popli" Date: Wed, 26 Mar 2025 12:29:16 +0530 Subject: [PATCH 12/21] fix: resolved comments --- .../requestPreProcessor/reqpreprocessor.go | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor.go b/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor.go index 009fe5b..36389dc 100644 --- a/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor.go +++ b/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor.go @@ -32,11 +32,7 @@ func NewUUIDSetter(cfg *Config) (func(http.Handler) http.Handler, error) { } return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - body, err := io.ReadAll(r.Body) - if err != nil { - http.Error(w, "Failed to read request body", http.StatusInternalServerError) - return - } + body, _ := io.ReadAll(r.Body) var req becknRequest if err := json.Unmarshal(body, &req); err != nil { http.Error(w, "Failed to decode request body", http.StatusBadRequest) @@ -60,11 +56,7 @@ func NewUUIDSetter(cfg *Config) (func(http.Handler) http.Handler, error) { ctx = context.WithValue(ctx, contextKeyType(key), updatedValue) } reqData := map[string]any{"context": req.Context} - updatedBody, err := json.Marshal(reqData) - if err != nil { - http.Error(w, "Failed to marshal updated JSON", http.StatusInternalServerError) - return - } + updatedBody, _ := json.Marshal(reqData) r.Body = io.NopCloser(bytes.NewBuffer(updatedBody)) r.ContentLength = int64(len(updatedBody)) r = r.WithContext(ctx) From 4a7e9d7b4f13578169ea07c5a719b7c07653bd14 Mon Sep 17 00:00:00 2001 From: "mayur.popli" Date: Wed, 26 Mar 2025 12:31:17 +0530 Subject: [PATCH 13/21] fix: resolved comments --- .../reqpreprocessor_test.go | 117 ++++++++++++------ 1 file changed, 78 insertions(+), 39 deletions(-) diff --git a/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go b/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go index ad11606..2ff382f 100644 --- a/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go +++ b/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go @@ -8,12 +8,11 @@ import ( "testing" ) -func TestNewUUIDSetter(t *testing.T) { +func TestNewUUIDSetter_SuccessCases(t *testing.T) { tests := []struct { name string config *Config requestBody map[string]any - expectedCode int expectedKeys []string role string }{ @@ -30,7 +29,6 @@ func TestNewUUIDSetter(t *testing.T) { "bap_id": "bap-123", }, }, - expectedCode: http.StatusOK, expectedKeys: []string{"transaction_id", "message_id", "bap_id"}, role: "bap", }, @@ -47,10 +45,79 @@ func TestNewUUIDSetter(t *testing.T) { "bpp_id": "bpp-456", }, }, - expectedCode: http.StatusOK, expectedKeys: []string{"transaction_id", "message_id", "bpp_id"}, role: "bpp", }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + middleware, err := NewUUIDSetter(tt.config) + if err != nil { + t.Fatalf("Unexpected error while creating middleware: %v", err) + } + + bodyBytes, _ := json.Marshal(tt.requestBody) + req := httptest.NewRequest(http.MethodPost, "/test", bytes.NewReader(bodyBytes)) + req.Header.Set("Content-Type", "application/json") + + rec := httptest.NewRecorder() + + dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + w.WriteHeader(http.StatusOK) + + subID, ok := ctx.Value(subscriberIDKey).(string) + if !ok { + http.Error(w, "Subscriber ID not found", http.StatusInternalServerError) + return + } + + response := map[string]any{"subscriber_id": subID} + if err := json.NewEncoder(w).Encode(response); err != nil { + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + }) + + middleware(dummyHandler).ServeHTTP(rec, req) + + if rec.Code != http.StatusOK { + t.Errorf("Expected status code 200, but got %d", rec.Code) + return + } + + var responseBody map[string]any + if err := json.Unmarshal(rec.Body.Bytes(), &responseBody); err != nil { + t.Fatal("Failed to unmarshal response body:", err) + } + + expectedSubIDKey := "bap_id" + if tt.role == "bpp" { + expectedSubIDKey = "bpp_id" + } + + subID, ok := responseBody["subscriber_id"].(string) + if !ok { + t.Error("subscriber_id not found in response") + return + } + + expectedSubID := tt.requestBody["context"].(map[string]any)[expectedSubIDKey] + if subID != expectedSubID { + t.Errorf("Expected subscriber_id %v, but got %v", expectedSubID, subID) + } + }) + } +} + +func TestNewUUIDSetter_ErrorCases(t *testing.T) { + tests := []struct { + name string + config *Config + requestBody map[string]any + expectedCode int + }{ { name: "Missing context key", config: &Config{ @@ -82,58 +149,30 @@ func TestNewUUIDSetter(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { middleware, err := NewUUIDSetter(tt.config) - if tt.config == nil || len(tt.config.CheckKeys) == 0 { + if tt.config == nil { if err == nil { - t.Fatal("Expected an error, but got none") + t.Error("Expected an error for nil config, but got none") } return } if err != nil { t.Fatalf("Unexpected error while creating middleware: %v", err) } + bodyBytes, _ := json.Marshal(tt.requestBody) req := httptest.NewRequest(http.MethodPost, "/test", bytes.NewReader(bodyBytes)) req.Header.Set("Content-Type", "application/json") + rec := httptest.NewRecorder() dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() w.WriteHeader(http.StatusOK) - if subID, ok := ctx.Value(subscriberIDKey).(string); ok { - response := map[string]any{ - "subscriber_id": subID, - } - if err := json.NewEncoder(w).Encode(response); err != nil { - http.Error(w, "Internal Server Error", http.StatusInternalServerError) - return - } - } else { - http.Error(w, "Subscriber ID not found", http.StatusInternalServerError) - return - } }) + middleware(dummyHandler).ServeHTTP(rec, req) + if rec.Code != tt.expectedCode { t.Errorf("Expected status code %d, but got %d", tt.expectedCode, rec.Code) } - if rec.Code == http.StatusOK { - var responseBody map[string]any - if err := json.Unmarshal(rec.Body.Bytes(), &responseBody); err != nil { - t.Fatal("Failed to unmarshal response body:", err) - } - expectedSubIDKey := "bap_id" - if tt.role == "bpp" { - expectedSubIDKey = "bpp_id" - } - - if subID, ok := responseBody["subscriber_id"].(string); ok { - expectedSubID := tt.requestBody["context"].(map[string]any)[expectedSubIDKey] - if subID != expectedSubID { - t.Errorf("Expected subscriber_id %v, but got %v", expectedSubID, subID) - } - } else { - t.Error("subscriber_id not found in response") - } - } }) } -} +} \ No newline at end of file From 523cd077c4e4b37d95ed1d1d1e15f8156819cbcb Mon Sep 17 00:00:00 2001 From: "mayur.popli" Date: Thu, 27 Mar 2025 08:30:27 +0530 Subject: [PATCH 14/21] fix: plugin test case --- .../requestPreProcessor/cmd/plugin_test.go | 84 +++++++++++++++++++ .../requestPreProcessor/reqpreprocessor.go | 7 ++ 2 files changed, 91 insertions(+) create mode 100644 pkg/plugin/implementation/requestPreProcessor/cmd/plugin_test.go diff --git a/pkg/plugin/implementation/requestPreProcessor/cmd/plugin_test.go b/pkg/plugin/implementation/requestPreProcessor/cmd/plugin_test.go new file mode 100644 index 0000000..956be52 --- /dev/null +++ b/pkg/plugin/implementation/requestPreProcessor/cmd/plugin_test.go @@ -0,0 +1,84 @@ +package main + +import ( + "context" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestProviderNew(t *testing.T) { + testCases := []struct { + name string + config map[string]string + expectedError bool + expectedStatus int + prepareRequest func(req *http.Request) + }{ + { + name: "No Config", + config: map[string]string{}, + expectedError: true, + expectedStatus: http.StatusOK, + prepareRequest: func(req *http.Request) { + // Add minimal required headers + req.Header.Set("context", "test-context") + req.Header.Set("transaction_id", "test-transaction") + }, + }, + { + name: "With Check Keys", + config: map[string]string{ + "CheckKeys": "message_id,transaction_id", + }, + expectedError: false, + expectedStatus: http.StatusOK, + prepareRequest: func(req *http.Request) { + // Add headers matching the check keys + req.Header.Set("context", "test-context") + req.Header.Set("transaction_id", "test-transaction") + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + requestBody := `{ + "context": { + "transaction_id": "abc" + } + }` + + p := provider{} + middleware, err := p.New(context.Background(), tc.config) + if tc.expectedError { + assert.Error(t, err) + return + } + require.NoError(t, err) + assert.NotNil(t, middleware) + + testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) + + req := httptest.NewRequest("POST", "/", strings.NewReader(requestBody)) + req.Header.Set("Content-Type", "application/json") + if tc.prepareRequest != nil { + tc.prepareRequest(req) + } + + w := httptest.NewRecorder() + middlewaredHandler := middleware(testHandler) + middlewaredHandler.ServeHTTP(w, req) + assert.Equal(t, tc.expectedStatus, w.Code, "Unexpected response status") + responseBody := w.Body.String() + t.Logf("Response Body: %s", responseBody) + + }) + } +} diff --git a/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor.go b/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor.go index 36389dc..031d4ce 100644 --- a/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor.go +++ b/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor.go @@ -88,6 +88,13 @@ func validateConfig(cfg *Config) error { if cfg == nil { return errors.New("config cannot be nil") } + + // Check if CheckKeys is empty + if len(cfg.CheckKeys) == 0 { + return errors.New("checkKeys cannot be empty") + } + + // Validate that CheckKeys does not contain empty strings for _, key := range cfg.CheckKeys { if key == "" { return errors.New("checkKeys cannot contain empty strings") From a9ffe29a6e0d330bd5db733aee84e1ab0d007cb6 Mon Sep 17 00:00:00 2001 From: tanyamadaan Date: Thu, 27 Mar 2025 11:28:45 +0530 Subject: [PATCH 15/21] updated router --- go.mod | 14 +- go.sum | 8 +- pkg/plugin/definition/router.go | 12 +- .../implementation/router/cmd/plugin.go | 2 +- .../implementation/router/cmd/plugin_test.go | 94 ++++---- pkg/plugin/implementation/router/router.go | 210 +++++++++-------- .../implementation/router/router_test.go | 216 ++++++++---------- .../router/testData/bap_caller.yaml | 6 +- .../router/testData/bap_receiver.yaml | 6 +- .../router/testData/bpp_caller.yaml | 4 +- .../router/testData/bpp_receiver.yaml | 8 +- pkg/plugin/manager.go | 24 +- 12 files changed, 282 insertions(+), 322 deletions(-) diff --git a/go.mod b/go.mod index c2692fb..8c19ef1 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,8 @@ module github.com/beckn/beckn-onix -go 1.23.4 +go 1.24 -toolchain go1.23.7 - -require ( - github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 - golang.org/x/crypto v0.36.0 -) +require golang.org/x/crypto v0.36.0 require ( github.com/kr/pretty v0.3.1 // indirect @@ -15,10 +10,7 @@ require ( gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) -require ( - github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03 - golang.org/x/text v0.23.0 // indirect -) +require github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03 require ( golang.org/x/sys v0.31.0 // indirect diff --git a/go.sum b/go.sum index 896f459..b4c0bb0 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,4 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= -github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -12,18 +10,14 @@ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsK github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03 h1:m1h+vudopHsI67FPT9MOncyndWhTcdUoBtI1R1uajGY= github.com/zenazn/pkcs7pad v0.0.0-20170308005700-253a5b1f0e03/go.mod h1:8sheVFH84v3PCyFY/O02mIgSQY9I6wMYPWsq7mDnEZY= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= \ No newline at end of file +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/plugin/definition/router.go b/pkg/plugin/definition/router.go index b9f7e41..399143d 100644 --- a/pkg/plugin/definition/router.go +++ b/pkg/plugin/definition/router.go @@ -5,19 +5,19 @@ import ( "net/url" ) -// Route defines the structure for the Route returned +// Route defines the structure for the Route returned. type Route struct { - RoutingType string // "url" or "msgq" - TopicID string // For message queues - TargetURL string // For API calls + TargetType string // "url" or "msgq" + PublisherID string // For message queues + URL string // 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. type RouterProvider interface { New(ctx context.Context, config map[string]string) (Router, func() error, error) } -// Router defines the interface for routing requests +// Router defines the interface for routing requests. type Router interface { // Route determines the routing destination based on the request context. Route(ctx context.Context, url *url.URL, body []byte) (*Route, error) diff --git a/pkg/plugin/implementation/router/cmd/plugin.go b/pkg/plugin/implementation/router/cmd/plugin.go index f38ffa9..23816ee 100644 --- a/pkg/plugin/implementation/router/cmd/plugin.go +++ b/pkg/plugin/implementation/router/cmd/plugin.go @@ -17,7 +17,7 @@ func (rp RouterProvider) New(ctx context.Context, config map[string]string) (def return nil, nil, errors.New("context cannot be nil") } - // Parse the routing_config key from the config map + // Parse the routingConfig key from the config map routingConfig, ok := config["routingConfig"] if !ok { return nil, nil, errors.New("routingConfig is required in the configuration") diff --git a/pkg/plugin/implementation/router/cmd/plugin_test.go b/pkg/plugin/implementation/router/cmd/plugin_test.go index afa0c97..08c9bd7 100644 --- a/pkg/plugin/implementation/router/cmd/plugin_test.go +++ b/pkg/plugin/implementation/router/cmd/plugin_test.go @@ -4,6 +4,7 @@ import ( "context" "os" "path/filepath" + "runtime" "strings" "testing" ) @@ -12,42 +13,23 @@ import ( func setupTestConfig(t *testing.T) string { t.Helper() - // Create a temporary directory for the routing rules - configDir, err := os.MkdirTemp("", "routingRules") + // Get project root (assuming testData is in project root) + _, filename, _, _ := runtime.Caller(0) // Path to plugin_test.go + projectRoot := filepath.Dir(filepath.Dir(filename)) // Move up from cmd/ + yamlPath := filepath.Join(projectRoot, "testData", "bap_receiver.yaml") + + // Copy to temp file (to test file loading logic) + tempDir := t.TempDir() + tempPath := filepath.Join(tempDir, "routingRules.yaml") + content, err := os.ReadFile(yamlPath) if err != nil { - t.Fatalf("Failed to create temp directory: %v", err) + t.Fatalf("Failed to read test file: %v", err) + } + if err := os.WriteFile(tempPath, content, 0644); err != nil { + t.Fatalf("Failed to create temp config: %v", err) } - // Define sample routing rules - rulesContent := ` -routingRules: - - domain: "ONDC:TRV11" - version: "2.0.0" - routingType: "url" - target: - url: "https://services-backend/trv/v1" - endpoints: - - select - - init - - confirm - - status - - - domain: "ONDC:TRV11" - version: "2.0.0" - routingType: "msgq" - target: - topic_id: "trv_topic_id1" - endpoints: - - search -` - - // Write the routing rules to a file - 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) - } - - return rulesFilePath + return tempPath } // TestRouterProviderSuccess tests the RouterProvider implementation for success cases. @@ -57,9 +39,10 @@ func TestRouterProviderSuccess(t *testing.T) { // Define test cases tests := []struct { - name string - ctx context.Context - config map[string]string + name string + ctx context.Context + config map[string]string + wantErr bool }{ { name: "Valid configuration", @@ -67,6 +50,7 @@ func TestRouterProviderSuccess(t *testing.T) { config: map[string]string{ "routingConfig": rulesFilePath, }, + wantErr: false, }, } @@ -76,14 +60,14 @@ func TestRouterProviderSuccess(t *testing.T) { router, _, err := provider.New(tt.ctx, tt.config) // Ensure no error occurred - if err != nil { - t.Errorf("unexpected error: %v", err) + if (err != nil) != tt.wantErr { + t.Errorf("New(%v, %v) error = %v, wantErr %v", tt.ctx, tt.config, err, tt.wantErr) return } // Ensure the router and close function are not nil if router == nil { - t.Error("expected a non-nil Router instance, got nil") + t.Errorf("New(%v, %v) = nil router, want non-nil", tt.ctx, tt.config) } }) } @@ -96,10 +80,10 @@ func TestRouterProviderFailure(t *testing.T) { // Define test cases tests := []struct { - name string - ctx context.Context - config map[string]string - expectedError string + name string + ctx context.Context + config map[string]string + wantErr string }{ { name: "Empty routing config path", @@ -107,19 +91,19 @@ func TestRouterProviderFailure(t *testing.T) { config: map[string]string{ "routingConfig": "", }, - expectedError: "failed to load routing rules: routingConfig path is empty", + wantErr: "failed to load routing rules: routingConfig path is empty", }, { - name: "Missing routing config key", - ctx: context.Background(), - config: map[string]string{}, - expectedError: "routingConfig is required in the configuration", + name: "Missing routing config key", + ctx: context.Background(), + config: map[string]string{}, + wantErr: "routingConfig is required in the configuration", }, { - name: "Nil context", - ctx: nil, - config: map[string]string{"routingConfig": rulesFilePath}, - expectedError: "context cannot be nil", + name: "Nil context", + ctx: nil, + config: map[string]string{"routingConfig": rulesFilePath}, + wantErr: "context cannot be nil", }, } @@ -129,8 +113,10 @@ func TestRouterProviderFailure(t *testing.T) { _, _, err := provider.New(tt.ctx, tt.config) // Check for expected error - if err == nil || !strings.Contains(err.Error(), tt.expectedError) { - t.Errorf("expected error %q, got %v", tt.expectedError, err) + if err == nil { + t.Errorf("New(%v, %v) = nil error, want error containing %q", tt.ctx, tt.config, tt.wantErr) + } else if !strings.Contains(err.Error(), tt.wantErr) { + t.Errorf("New(%v, %v) = %v, want error containing %q", tt.ctx, tt.config, err, tt.wantErr) } }) } diff --git a/pkg/plugin/implementation/router/router.go b/pkg/plugin/implementation/router/router.go index 2649dbd..ae98baf 100644 --- a/pkg/plugin/implementation/router/router.go +++ b/pkg/plugin/implementation/router/router.go @@ -24,27 +24,34 @@ type routingConfig struct { RoutingRules []routingRule `yaml:"routingRules"` } -// Router implements Router interface +// Router implements Router interface. type Router struct { - config *Config - rules []routingRule + rules map[string]map[string]map[string]*definition.Route // domain -> version -> endpoint -> route } // RoutingRule represents a single routing rule. type routingRule struct { - Domain string `yaml:"domain"` - Version string `yaml:"version"` - RoutingType string `yaml:"routingType"` // "url", "msgq", "bpp", or "bap" - Target target `yaml:"target,omitempty"` - Endpoints []string `yaml:"endpoints"` + Domain string `yaml:"domain"` + Version string `yaml:"version"` + TargetType string `yaml:"targetType"` // "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"` // URL for "url" or gateway endpoint for "bpp"/"bap" - TopicID string `yaml:"topic_id,omitempty"` // For "msgq" type + URL string `yaml:"url,omitempty"` // URL for "url" or gateway endpoint for "bpp"/"bap" + PublisherID string `yaml:"publisherId,omitempty"` // For "msgq" type } +// TargetType defines possible target destinations. +const ( + targetTypeURL = "url" // Route to a specific URL + targetTypeMSGQ = "msgq" // Route to a message queue + targetTypeBPP = "bpp" // Route to a BPP endpoint + targetTypeBAP = "bap" // Route to a BAP endpoint +) + // 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. @@ -54,24 +61,24 @@ func New(ctx context.Context, config *Config) (*Router, func() error, error) { return nil, nil, fmt.Errorf("config cannot be nil") } router := &Router{ - config: config, + rules: make(map[string]map[string]map[string]*definition.Route), } // Load rules at bootup - if err := router.loadRules(); err != nil { + if err := router.loadRules(config.RoutingConfig); err != nil { return nil, nil, fmt.Errorf("failed to load routing rules: %w", err) } return router, nil, nil } // LoadRules reads and parses routing rules from the YAML configuration file. -func (r *Router) loadRules() error { - if r.config.RoutingConfig == "" { +func (r *Router) loadRules(configPath string) error { + if configPath == "" { return fmt.Errorf("routingConfig path is empty") } - data, err := os.ReadFile(r.config.RoutingConfig) + data, err := os.ReadFile(configPath) if err != nil { - return fmt.Errorf("error reading config file at %s: %w", r.config.RoutingConfig, err) + return fmt.Errorf("error reading config file at %s: %w", configPath, err) } var config routingConfig if err := yaml.Unmarshal(data, &config); err != nil { @@ -82,36 +89,74 @@ func (r *Router) loadRules() error { if err := validateRules(config.RoutingRules); err != nil { return fmt.Errorf("invalid routing rules: %w", err) } - r.rules = config.RoutingRules + // Build the optimized rule map + for _, rule := range config.RoutingRules { + // Initialize domain map if not exists + if _, ok := r.rules[rule.Domain]; !ok { + r.rules[rule.Domain] = make(map[string]map[string]*definition.Route) + } + + // Initialize version map if not exists + if _, ok := r.rules[rule.Domain][rule.Version]; !ok { + r.rules[rule.Domain][rule.Version] = make(map[string]*definition.Route) + } + + // Add all endpoints for this rule + for _, endpoint := range rule.Endpoints { + var route *definition.Route + switch rule.TargetType { + case targetTypeMSGQ: + route = &definition.Route{ + TargetType: rule.TargetType, + PublisherID: rule.Target.PublisherID, + } + case targetTypeURL: + route = &definition.Route{ + TargetType: rule.TargetType, + URL: rule.Target.URL, + } + case targetTypeBPP, targetTypeBAP: + route = &definition.Route{ + TargetType: rule.TargetType, + URL: rule.Target.URL, // Fallback URL if URI not provided in request + } + } + + fmt.Print(r.rules) + + r.rules[rule.Domain][rule.Version][endpoint] = route + } + } + return nil } // 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 routingType are required") + // Ensure domain, version, and TargetType are present + if rule.Domain == "" || rule.Version == "" || rule.TargetType == "" { + return fmt.Errorf("invalid rule: domain, version, and targetType are required") } - // Validate based on routingType - switch rule.RoutingType { - case "url": + // Validate based on TargetType + switch rule.TargetType { + case targetTypeURL: if rule.Target.URL == "" { - return fmt.Errorf("invalid rule: url is required for routingType 'url'") + return fmt.Errorf("invalid rule: url is required for targetType '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: topicId is required for routingType 'msgq'") + case targetTypeMSGQ: + if rule.Target.PublisherID == "" { + return fmt.Errorf("invalid rule: publisherID is required for targetType 'msgq'") } - case "bpp", "bap": + case targetTypeBPP, targetTypeBAP: // No target validation needed for bpp/bap, as they use URIs from the request body continue default: - return fmt.Errorf("invalid rule: unknown routingType '%s'", rule.RoutingType) + return fmt.Errorf("invalid rule: unknown targetType '%s'", rule.TargetType) } } return nil @@ -125,8 +170,8 @@ 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"` + BPPURI string `json:"bpp_uri,omitempty"` + BAPURI string `json:"bap_uri,omitempty"` } `json:"context"` } if err := json.Unmarshal(body, &requestBody); err != nil { @@ -136,69 +181,52 @@ func (r *Router) Route(ctx context.Context, url *url.URL, body []byte) (*definit // 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) + // Lookup route in the optimized map + domainRules, ok := r.rules[requestBody.Context.Domain] + if !ok { + return nil, fmt.Errorf("no routing rules found for domain %s", requestBody.Context.Domain) } - // Match the rule - 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) - } - } + versionRules, ok := domainRules[requestBody.Context.Version] + if !ok { + return nil, fmt.Errorf("no routing rules found for domain %s version %s", requestBody.Context.Domain, requestBody.Context.Version) + } + + route, ok := versionRules[endpoint] + if !ok { + 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) + } + + // Handle BPP/BAP routing with request URIs + switch route.TargetType { + case targetTypeBPP: + uri := strings.TrimSpace(requestBody.Context.BPPURI) + 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: + uri := strings.TrimSpace(requestBody.Context.BAPURI) + 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 BAP URI nor was a default URL configured in routing rules", endpoint) + } + route = &definition.Route{ + TargetType: route.TargetType, + URL: target, } } - // 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) -} - -// 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 + return route, nil } diff --git a/pkg/plugin/implementation/router/router_test.go b/pkg/plugin/implementation/router/router_test.go index 3d3fe70..f67b577 100644 --- a/pkg/plugin/implementation/router/router_test.go +++ b/pkg/plugin/implementation/router/router_test.go @@ -15,35 +15,19 @@ var testData embed.FS func setupTestConfig(t *testing.T, yamlFileName string) string { t.Helper() + configDir := t.TempDir() - // Create a temporary directory for the routing rules - configDir, err := os.MkdirTemp("", "routing_rules") + content, err := testData.ReadFile("testData/" + yamlFileName) if err != nil { - t.Fatalf("Failed to create temp directory: %v", err) + t.Fatalf("ReadFile() err = %v, want nil", err) } - // 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(yamlContent), 0644); err != nil { - t.Fatalf("Failed to write routing rules file: %v", err) + rulesPath := filepath.Join(configDir, "routing_rules.yaml") + if err := os.WriteFile(rulesPath, content, 0644); err != nil { + t.Fatalf("WriteFile() err = %v, want nil", 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) + return rulesPath } // setupRouter is a helper function to create router instance. @@ -78,35 +62,35 @@ func TestNew(t *testing.T) { // Define test cases tests := []struct { - name string - config *Config - expectedError string + name string + config *Config + wantErr string }{ { name: "Valid configuration", config: &Config{ RoutingConfig: rulesFilePath, }, - expectedError: "", + wantErr: "", }, { - name: "Empty config", - config: nil, - expectedError: "config cannot be nil", + name: "Empty config", + config: nil, + wantErr: "config cannot be nil", }, { name: "Empty routing config path", config: &Config{ RoutingConfig: "", }, - expectedError: "routingConfig path is empty", + wantErr: "routingConfig path is empty", }, { name: "Routing config file does not exist", config: &Config{ RoutingConfig: "/nonexistent/path/to/rules.yaml", }, - expectedError: "error reading config file", + wantErr: "error reading config file", }, } @@ -115,22 +99,22 @@ func TestNew(t *testing.T) { router, _, err := New(ctx, tt.config) // 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) + if tt.wantErr != "" { + if err == nil || !strings.Contains(err.Error(), tt.wantErr) { + t.Errorf("New(%v) = %v, want error containing %q", tt.config, err, tt.wantErr) } return } // Ensure no error occurred if err != nil { - t.Errorf("unexpected error: %v", err) + t.Errorf("New(%v) = %v, want nil error", tt.config, err) return } // Ensure the router and close function are not nil if router == nil { - t.Error("expected a non-nil Router instance, got nil") + t.Errorf("New(%v, %v) = nil router, want non-nil", ctx, tt.config) } }) } @@ -148,9 +132,9 @@ func TestValidateRulesSuccess(t *testing.T) { name: "Valid rules with url routing", rules: []routingRule{ { - Domain: "retail", - Version: "1.0.0", - RoutingType: "url", + Domain: "retail", + Version: "1.0.0", + TargetType: "url", Target: target{ URL: "https://example.com/api", }, @@ -162,11 +146,11 @@ func TestValidateRulesSuccess(t *testing.T) { name: "Valid rules with msgq routing", rules: []routingRule{ { - Domain: "retail", - Version: "1.0.0", - RoutingType: "msgq", + Domain: "retail", + Version: "1.0.0", + TargetType: "msgq", Target: target{ - TopicID: "example_topic", + PublisherID: "example_topic", }, Endpoints: []string{"on_search", "on_select"}, }, @@ -176,9 +160,9 @@ func TestValidateRulesSuccess(t *testing.T) { name: "Valid rules with bpp routing to gateway", rules: []routingRule{ { - Domain: "retail", - Version: "1.0.0", - RoutingType: "bpp", + Domain: "retail", + Version: "1.0.0", + TargetType: "bpp", Target: target{ URL: "https://mock_gateway.com/api", }, @@ -190,10 +174,10 @@ func TestValidateRulesSuccess(t *testing.T) { name: "Valid rules with bpp routing", rules: []routingRule{ { - Domain: "retail", - Version: "1.0.0", - RoutingType: "bpp", - Endpoints: []string{"select"}, + Domain: "retail", + Version: "1.0.0", + TargetType: "bpp", + Endpoints: []string{"select"}, }, }, }, @@ -201,10 +185,10 @@ func TestValidateRulesSuccess(t *testing.T) { name: "Valid rules with bap routing", rules: []routingRule{ { - Domain: "retail", - Version: "1.0.0", - RoutingType: "bap", - Endpoints: []string{"select"}, + Domain: "retail", + Version: "1.0.0", + TargetType: "bap", + Endpoints: []string{"select"}, }, }, }, @@ -214,7 +198,7 @@ func TestValidateRulesSuccess(t *testing.T) { t.Run(tt.name, func(t *testing.T) { err := validateRules(tt.rules) if err != nil { - t.Errorf("unexpected error: %v", err) + t.Errorf("validateRules(%v) = %v, want nil error", tt.rules, err) } }) } @@ -223,40 +207,40 @@ func TestValidateRulesSuccess(t *testing.T) { // TestValidateRulesFailure tests the validate function for failure cases. func TestValidateRulesFailure(t *testing.T) { tests := []struct { - name string - rules []routingRule - expectedErr string + name string + rules []routingRule + wantErr string }{ { name: "Missing domain", rules: []routingRule{ { - Version: "1.0.0", - RoutingType: "url", + Version: "1.0.0", + TargetType: "url", Target: target{ URL: "https://example.com/api", }, Endpoints: []string{"search", "select"}, }, }, - expectedErr: "invalid rule: domain, version, and routingType are required", + wantErr: "invalid rule: domain, version, and targetType are required", }, { name: "Missing version", rules: []routingRule{ { - Domain: "retail", - RoutingType: "url", + Domain: "retail", + TargetType: "url", Target: target{ URL: "https://example.com/api", }, Endpoints: []string{"search", "select"}, }, }, - expectedErr: "invalid rule: domain, version, and routingType are required", + wantErr: "invalid rule: domain, version, and targetType are required", }, { - name: "Missing routingType", + name: "Missing targetType", rules: []routingRule{ { Domain: "retail", @@ -267,62 +251,60 @@ func TestValidateRulesFailure(t *testing.T) { Endpoints: []string{"search", "select"}, }, }, - expectedErr: "invalid rule: domain, version, and routingType are required", + wantErr: "invalid rule: domain, version, and targetType are required", }, { - name: "Invalid routingType", + name: "Invalid targetType", rules: []routingRule{ { - Domain: "retail", - Version: "1.0.0", - RoutingType: "invalid", + Domain: "retail", + Version: "1.0.0", + TargetType: "invalid", Target: target{ URL: "https://example.com/api", }, Endpoints: []string{"search", "select"}, }, }, - expectedErr: "invalid rule: unknown routingType 'invalid'", + wantErr: "invalid rule: unknown targetType 'invalid'", }, { - name: "Missing url for routingType: url", + name: "Missing url for targetType: url", rules: []routingRule{ { - Domain: "retail", - Version: "1.0.0", - RoutingType: "url", - Target: target{ + Domain: "retail", + Version: "1.0.0", + TargetType: "url", + Target: target{ // URL is missing }, Endpoints: []string{"search", "select"}, }, }, - expectedErr: "invalid rule: url is required for routingType 'url'", + wantErr: "invalid rule: url is required for targetType 'url'", }, { - name: "Missing topic_id for routingType: msgq", + name: "Missing topic_id for targetType: msgq", rules: []routingRule{ { - Domain: "retail", - Version: "1.0.0", - RoutingType: "msgq", - Target: target{ - // TopicID is missing + Domain: "retail", + Version: "1.0.0", + TargetType: "msgq", + Target: target{ + // PublisherID is missing }, Endpoints: []string{"search", "select"}, }, }, - expectedErr: "invalid rule: topicId is required for routingType 'msgq'", + wantErr: "invalid rule: publisherID is required for targetType 'msgq'", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := validateRules(tt.rules) - if err == nil { - t.Errorf("expected error: %v, got nil", tt.expectedErr) - } else if err.Error() != tt.expectedErr { - t.Errorf("expected error: %v, got: %v", tt.expectedErr, err) + if err == nil || !strings.Contains(err.Error(), tt.wantErr) { + t.Errorf("validateRules(%v) = %v, want error containing %q", tt.rules, err, tt.wantErr) } }) } @@ -387,7 +369,7 @@ func TestRouteSuccess(t *testing.T) { // Ensure no error occurred if err != nil { - t.Errorf("unexpected error: %v", err) + t.Errorf("router.Route(%v, %v, %v) = %v, want nil error", ctx, parsedURL, []byte(tt.body), err) } }) } @@ -399,39 +381,39 @@ func TestRouteFailure(t *testing.T) { // Define failure test cases tests := []struct { - name string - configFile string - url string - body string - expectedError string + name string + configFile string + url string + body string + wantErr 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: "Unsupported endpoint", + configFile: "bpp_receiver.yaml", + url: "https://example.com/v1/ondc/unsupported", + body: `{"context": {"domain": "ONDC:TRV11", "version": "2.0.0"}}`, + wantErr: "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: "No matching rule", + configFile: "bpp_receiver.yaml", + url: "https://example.com/v1/ondc/select", + body: `{"context": {"domain": "ONDC:SRV11", "version": "2.0.0"}}`, + wantErr: "no routing rules found for domain ONDC:SRV11", }, { - 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 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"}}`, + wantErr: "could not determine destination for endpoint 'on_search': neither request contained a BAP URI nor was a default URL configured in routing rules", }, { - 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", + 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"}}`, + wantErr: "could not determine destination for endpoint 'select': neither request contained a BPP URI nor was a default URL configured in routing rules", }, } @@ -444,8 +426,8 @@ func TestRouteFailure(t *testing.T) { _, 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) + if err == nil || !strings.Contains(err.Error(), tt.wantErr) { + t.Errorf("Route(%q, %q) = %v, want error containing %q", tt.url, tt.body, err, tt.wantErr) } }) } diff --git a/pkg/plugin/implementation/router/testData/bap_caller.yaml b/pkg/plugin/implementation/router/testData/bap_caller.yaml index b1d5a44..0c595a6 100644 --- a/pkg/plugin/implementation/router/testData/bap_caller.yaml +++ b/pkg/plugin/implementation/router/testData/bap_caller.yaml @@ -1,14 +1,14 @@ routingRules: - domain: "ONDC:TRV10" version: "2.0.0" - routingType: "bpp" + targetType: "bpp" target: url: "https://gateway.example.com" endpoints: - search - domain: "ONDC:TRV10" version: "2.0.0" - routingType: "bpp" + targetType: "bpp" endpoints: - select - init @@ -17,7 +17,7 @@ routingRules: - cancel - domain: "ONDC:TRV12" version: "2.0.0" - routingType: "bpp" + targetType: "bpp" endpoints: - select - init diff --git a/pkg/plugin/implementation/router/testData/bap_receiver.yaml b/pkg/plugin/implementation/router/testData/bap_receiver.yaml index ca4a478..353ecc7 100644 --- a/pkg/plugin/implementation/router/testData/bap_receiver.yaml +++ b/pkg/plugin/implementation/router/testData/bap_receiver.yaml @@ -1,7 +1,7 @@ routingRules: - domain: "ONDC:TRV10" version: "2.0.0" - routingType: "url" + targetType: "url" target: url: "https://services-backend/trv/v1" endpoints: @@ -13,8 +13,8 @@ routingRules: - on_cancel - domain: "ONDC:TRV10" version: "2.0.0" - routingType: "msgq" + targetType: "msgq" target: - topic_id: "trv_topic_id1" + publisherId: "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 index 0d9a670..8d0c212 100644 --- a/pkg/plugin/implementation/router/testData/bpp_caller.yaml +++ b/pkg/plugin/implementation/router/testData/bpp_caller.yaml @@ -1,7 +1,7 @@ routingRules: - domain: "ONDC:TRV10" version: "2.0.0" - routingType: "bap" + targetType: "bap" endpoints: - on_search - on_select @@ -12,7 +12,7 @@ routingRules: - on_cancel - domain: "ONDC:TRV11" version: "2.0.0" - routingType: "bap" + targetType: "bap" endpoints: - on_search - on_select diff --git a/pkg/plugin/implementation/router/testData/bpp_receiver.yaml b/pkg/plugin/implementation/router/testData/bpp_receiver.yaml index 6febce6..9633923 100644 --- a/pkg/plugin/implementation/router/testData/bpp_receiver.yaml +++ b/pkg/plugin/implementation/router/testData/bpp_receiver.yaml @@ -1,7 +1,7 @@ routingRules: - domain: "ONDC:TRV10" version: "2.0.0" - routingType: "url" + targetType: "url" target: url: "https://services-backend/trv/v1" endpoints: @@ -13,15 +13,15 @@ routingRules: - domain: "ONDC:TRV10" version: "2.0.0" - routingType: "msgq" + targetType: "msgq" target: - topic_id: "trv_topic_id1" + publisherId: "trv_topic_id1" endpoints: - search - domain: "ONDC:TRV11" version: "2.0.0" - routingType: "url" + targetType: "url" target: url: "https://services-backend/trv/v1" endpoints: diff --git a/pkg/plugin/manager.go b/pkg/plugin/manager.go index 6a4cecd..86a0b02 100644 --- a/pkg/plugin/manager.go +++ b/pkg/plugin/manager.go @@ -18,7 +18,6 @@ type Config struct { Decrypter PluginConfig `yaml:"decrypter"` Encrypter PluginConfig `yaml:"encrypter"` Publisher PluginConfig `yaml:"publisher"` - Router PluginConfig `yaml:"router"` } // PluginConfig represents configuration details for a plugin. @@ -34,7 +33,6 @@ type Manager struct { dp definition.DecrypterProvider ep definition.EncrypterProvider pb definition.PublisherProvider - rp definition.RouterProvider cfg *Config } @@ -74,13 +72,7 @@ func NewManager(ctx context.Context, cfg *Config) (*Manager, error) { return nil, fmt.Errorf("failed to load encryption plugin: %w", err) } - // Load router plugin. - rp, err := provider[definition.RouterProvider](cfg.Root, cfg.Router.ID) - if err != nil { - return nil, fmt.Errorf("failed to load encryption plugin: %w", err) - } - - return &Manager{sp: sp, vp: vp, pb: pb, ep: ep, dp: dp, rp: rp, cfg: cfg}, nil + return &Manager{sp: sp, vp: vp, pb: pb, ep: ep, dp: dp, cfg: cfg}, nil } // provider loads a plugin dynamically and retrieves its provider instance. @@ -177,17 +169,3 @@ func (m *Manager) Publisher(ctx context.Context) (definition.Publisher, error) { } return publisher, nil } - -// Router retrieves the router plugin instances. -func (m *Manager) Router(ctx context.Context) (definition.Router, func() error, error) { - if m.rp == nil { - return nil, nil, fmt.Errorf("router plugin provider not loaded") - - } - schemaValidator, close, err := m.rp.New(ctx, m.cfg.Router.Config) - if err != nil { - - return nil, nil, fmt.Errorf("failed to initialize schema validator: %v", err) - } - return schemaValidator, close, nil -} From 086375e063af30a9301929b77a784750ec91d73d Mon Sep 17 00:00:00 2001 From: tanyamadaan Date: Thu, 27 Mar 2025 17:28:39 +0530 Subject: [PATCH 16/21] Resolving merge conflicts --- pkg/plugin/definition/schemaValidator.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 pkg/plugin/definition/schemaValidator.go diff --git a/pkg/plugin/definition/schemaValidator.go b/pkg/plugin/definition/schemaValidator.go new file mode 100644 index 0000000..dc69ca5 --- /dev/null +++ b/pkg/plugin/definition/schemaValidator.go @@ -0,0 +1,16 @@ +package definition + +import ( + "context" + "net/url" +) + +// SchemaValidator interface for schema validation. +type SchemaValidator interface { + Validate(ctx context.Context, url *url.URL, payload []byte) error +} + +// SchemaValidatorProvider interface for creating validators. +type SchemaValidatorProvider interface { + New(ctx context.Context, config map[string]string) (SchemaValidator, func() error, error) +} From dff7a5abb329e0d5e49f083c9f9c1498127ddca8 Mon Sep 17 00:00:00 2001 From: tanyamadaan Date: Fri, 28 Mar 2025 12:20:08 +0530 Subject: [PATCH 17/21] Changes as per integration testing --- pkg/plugin/definition/router.go | 6 +- .../implementation/router/cmd/plugin.go | 2 +- pkg/plugin/implementation/router/router.go | 121 ++++++++++++------ .../implementation/router/router_test.go | 64 ++++++++- .../router/testData/bap_receiver.yaml | 2 +- .../router/testData/bpp_receiver.yaml | 4 +- 6 files changed, 146 insertions(+), 53 deletions(-) diff --git a/pkg/plugin/definition/router.go b/pkg/plugin/definition/router.go index 21b1be6..05e2e30 100644 --- a/pkg/plugin/definition/router.go +++ b/pkg/plugin/definition/router.go @@ -7,9 +7,9 @@ import ( // Route defines the structure for the Route returned. type Route struct { - TargetType string // "url" or "msgq" or "bap" or "bpp" - PublisherID string // For message queues - URL string // For API calls + TargetType string // "url" or "msgq" or "bap" or "bpp" + PublisherID string // For message queues + URL *url.URL // For API calls } // RouterProvider initializes the a new Router instance with the given config. diff --git a/pkg/plugin/implementation/router/cmd/plugin.go b/pkg/plugin/implementation/router/cmd/plugin.go index 23816ee..556f129 100644 --- a/pkg/plugin/implementation/router/cmd/plugin.go +++ b/pkg/plugin/implementation/router/cmd/plugin.go @@ -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. -var Provider definition.RouterProvider = RouterProvider{} +var Provider = RouterProvider{} diff --git a/pkg/plugin/implementation/router/router.go b/pkg/plugin/implementation/router/router.go index ae98baf..9d16767 100644 --- a/pkg/plugin/implementation/router/router.go +++ b/pkg/plugin/implementation/router/router.go @@ -33,7 +33,7 @@ type Router struct { type routingRule struct { Domain string `yaml:"domain"` 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"` Endpoints []string `yaml:"endpoints"` } @@ -46,10 +46,10 @@ type target struct { // TargetType defines possible target destinations. const ( - targetTypeURL = "url" // Route to a specific URL - targetTypeMSGQ = "msgq" // Route to a message queue - targetTypeBPP = "bpp" // Route to a BPP endpoint - targetTypeBAP = "bap" // Route to a BAP endpoint + targetTypeURL = "url" // Route to a specific URL + targetTypePublisher = "publisher" // Route to a publisher + targetTypeBPP = "bpp" // Route to a BPP endpoint + targetTypeBAP = "bap" // Route to a BAP endpoint ) // New initializes a new Router instance with the provided configuration. @@ -71,6 +71,30 @@ func New(ctx context.Context, config *Config) (*Router, func() error, error) { 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. func (r *Router) loadRules(configPath string) error { if configPath == "" { @@ -105,25 +129,33 @@ func (r *Router) loadRules(configPath string) error { for _, endpoint := range rule.Endpoints { var route *definition.Route switch rule.TargetType { - case targetTypeMSGQ: + case targetTypePublisher: route = &definition.Route{ TargetType: rule.TargetType, PublisherID: rule.Target.PublisherID, } case targetTypeURL: + parsedURL, err := parseTargetURL(rule.Target.URL) + if err != nil { + return fmt.Errorf("invalid URL in rule: %w", err) + } route = &definition.Route{ TargetType: rule.TargetType, - URL: rule.Target.URL, + URL: parsedURL, } 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{ 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 } } @@ -145,15 +177,19 @@ func validateRules(rules []routingRule) error { if rule.Target.URL == "" { return fmt.Errorf("invalid rule: url is required for targetType 'url'") } - if _, err := url.ParseRequestURI(rule.Target.URL); err != nil { - return fmt.Errorf("invalid URL in rule: %w", err) + if _, err := parseTargetURL(rule.Target.URL); err != nil { + return fmt.Errorf("invalid URL - %s: %w", rule.Target.URL, err) } - case targetTypeMSGQ: + case targetTypePublisher: 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: - // 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 default: 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", endpoint, requestBody.Context.Domain, requestBody.Context.Version) } - // Handle BPP/BAP routing with request URIs switch route.TargetType { case targetTypeBPP: - uri := strings.TrimSpace(requestBody.Context.BPPURI) - 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, - } + return handleProtocolMapping(route, requestBody.Context.BPPURI, endpoint) case targetTypeBAP: - uri := strings.TrimSpace(requestBody.Context.BAPURI) - target := strings.TrimSpace(route.URL) - if len(uri) != 0 { - target = uri + return handleProtocolMapping(route, requestBody.Context.BAPURI, endpoint) + } + 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 { + 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 { - 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) + targetURL = parsedURL + } + + // If no request URI, fall back to configured URL with endpoint appended + 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)) } - route = &definition.Route{ - TargetType: route.TargetType, - URL: target, + + 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 } diff --git a/pkg/plugin/implementation/router/router_test.go b/pkg/plugin/implementation/router/router_test.go index f67b577..7937ca5 100644 --- a/pkg/plugin/implementation/router/router_test.go +++ b/pkg/plugin/implementation/router/router_test.go @@ -143,12 +143,12 @@ func TestValidateRulesSuccess(t *testing.T) { }, }, { - name: "Valid rules with msgq routing", + name: "Valid rules with publisher routing", rules: []routingRule{ { Domain: "retail", Version: "1.0.0", - TargetType: "msgq", + TargetType: "publisher", Target: target{ PublisherID: "example_topic", }, @@ -284,19 +284,64 @@ func TestValidateRulesFailure(t *testing.T) { 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{ { Domain: "retail", 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{ // PublisherID is missing }, 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"}}`, }, { - name: "Valid domain, version, and endpoint (msgq routing)", + name: "Valid domain, version, and endpoint (publisher routing)", configFile: "bpp_receiver.yaml", url: "https://example.com/v1/ondc/search", 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"}}`, 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 { diff --git a/pkg/plugin/implementation/router/testData/bap_receiver.yaml b/pkg/plugin/implementation/router/testData/bap_receiver.yaml index 353ecc7..af98401 100644 --- a/pkg/plugin/implementation/router/testData/bap_receiver.yaml +++ b/pkg/plugin/implementation/router/testData/bap_receiver.yaml @@ -13,7 +13,7 @@ routingRules: - on_cancel - domain: "ONDC:TRV10" version: "2.0.0" - targetType: "msgq" + targetType: "publisher" target: publisherId: "trv_topic_id1" endpoints: diff --git a/pkg/plugin/implementation/router/testData/bpp_receiver.yaml b/pkg/plugin/implementation/router/testData/bpp_receiver.yaml index 9633923..a8f668e 100644 --- a/pkg/plugin/implementation/router/testData/bpp_receiver.yaml +++ b/pkg/plugin/implementation/router/testData/bpp_receiver.yaml @@ -10,15 +10,13 @@ routingRules: - confirm - status - cancel - - domain: "ONDC:TRV10" version: "2.0.0" - targetType: "msgq" + targetType: "publisher" target: publisherId: "trv_topic_id1" endpoints: - search - - domain: "ONDC:TRV11" version: "2.0.0" targetType: "url" From c64224032ba23a01505a080115c4603263924547 Mon Sep 17 00:00:00 2001 From: "mayur.popli" Date: Fri, 28 Mar 2025 14:16:50 +0530 Subject: [PATCH 18/21] fix: resolved comments --- pkg/response/response_test.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pkg/response/response_test.go b/pkg/response/response_test.go index 242fa72..73dcb6a 100644 --- a/pkg/response/response_test.go +++ b/pkg/response/response_test.go @@ -301,3 +301,17 @@ func compareContexts(c1, c2 map[string]interface{}) bool { return reflect.DeepEqual(c1, c2) } + +func TestSchemaValidationErr_Error(t *testing.T) { + validationErrors := []Error{ + {Paths: "name", Message: "Name is required"}, + {Paths: "email", Message: "Invalid email format"}, + } + err := SchemaValidationErr{Errors: validationErrors} + expected := "name: Name is required; email: Invalid email format" + if err.Error() != expected { + t.Errorf("err.Error() = %s, want %s", + err.Error(), expected) + + } +} From ead22119604a8325bf339ede7bb57b3fd9e7b13a Mon Sep 17 00:00:00 2001 From: "mayur.popli" Date: Fri, 28 Mar 2025 16:09:31 +0530 Subject: [PATCH 19/21] fix: resolved comments --- .../requestPreProcessor/cmd/plugin.go | 4 ++-- .../requestPreProcessor/cmd/plugin_test.go | 1 + .../requestPreProcessor/reqpreprocessor.go | 18 +++++++++--------- .../reqpreprocessor_test.go | 16 ++++++++-------- 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/pkg/plugin/implementation/requestPreProcessor/cmd/plugin.go b/pkg/plugin/implementation/requestPreProcessor/cmd/plugin.go index e8280d9..4a05ecc 100644 --- a/pkg/plugin/implementation/requestPreProcessor/cmd/plugin.go +++ b/pkg/plugin/implementation/requestPreProcessor/cmd/plugin.go @@ -12,8 +12,8 @@ type provider struct{} func (p provider) New(ctx context.Context, c map[string]string) (func(http.Handler) http.Handler, error) { config := &requestpreprocessor.Config{} - if checkKeysStr, ok := c["CheckKeys"]; ok { - config.CheckKeys = strings.Split(checkKeysStr, ",") + if contextKeysStr, ok := c["ContextKeys"]; ok { + config.ContextKeys = strings.Split(contextKeysStr, ",") } return requestpreprocessor.NewUUIDSetter(config) } diff --git a/pkg/plugin/implementation/requestPreProcessor/cmd/plugin_test.go b/pkg/plugin/implementation/requestPreProcessor/cmd/plugin_test.go index 4a8aae7..8b4cfe9 100644 --- a/pkg/plugin/implementation/requestPreProcessor/cmd/plugin_test.go +++ b/pkg/plugin/implementation/requestPreProcessor/cmd/plugin_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" ) +// TODO: Will Split this into success and fail (two test cases) func TestProviderNew(t *testing.T) { testCases := []struct { name string diff --git a/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor.go b/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor.go index ed2abda..13d4da0 100644 --- a/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor.go +++ b/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor.go @@ -13,8 +13,8 @@ import ( ) type Config struct { - CheckKeys []string - Role string + ContextKeys []string + Role string } type becknRequest struct { @@ -50,7 +50,7 @@ func NewUUIDSetter(cfg *Config) (func(http.Handler) http.Handler, error) { subID = req.Context["bpp_id"] } ctx := context.WithValue(r.Context(), subscriberIDKey, subID) - for _, key := range cfg.CheckKeys { + for _, key := range cfg.ContextKeys { value := uuid.NewString() updatedValue := update(req.Context, key, value) ctx = context.WithValue(ctx, contextKeyType(key), updatedValue) @@ -90,15 +90,15 @@ func validateConfig(cfg *Config) error { return errors.New("config cannot be nil") } - // Check if CheckKeys is empty. - if len(cfg.CheckKeys) == 0 { - return errors.New("checkKeys cannot be empty") + // Check if ContextKeys is empty. + if len(cfg.ContextKeys) == 0 { + return errors.New("ContextKeys cannot be empty") } - // Validate that CheckKeys does not contain empty strings. - for _, key := range cfg.CheckKeys { + // Validate that ContextKeys does not contain empty strings. + for _, key := range cfg.ContextKeys { if key == "" { - return errors.New("checkKeys cannot contain empty strings") + return errors.New("ContextKeys cannot contain empty strings") } } return nil diff --git a/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go b/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go index 3e64156..307a7e7 100644 --- a/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go +++ b/pkg/plugin/implementation/requestPreProcessor/reqpreprocessor_test.go @@ -19,8 +19,8 @@ func TestNewUUIDSetterSuccessCases(t *testing.T) { { name: "Valid keys, update missing keys with bap role", config: &Config{ - CheckKeys: []string{"transaction_id", "message_id"}, - Role: "bap", + ContextKeys: []string{"transaction_id", "message_id"}, + Role: "bap", }, requestBody: map[string]any{ "context": map[string]any{ @@ -35,8 +35,8 @@ func TestNewUUIDSetterSuccessCases(t *testing.T) { { name: "Valid keys, do not update existing keys with bpp role", config: &Config{ - CheckKeys: []string{"transaction_id", "message_id"}, - Role: "bpp", + ContextKeys: []string{"transaction_id", "message_id"}, + Role: "bpp", }, requestBody: map[string]any{ "context": map[string]any{ @@ -66,7 +66,7 @@ func TestNewUUIDSetterSuccessCases(t *testing.T) { dummyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ctx := r.Context() w.WriteHeader(http.StatusOK) - + subID, ok := ctx.Value(subscriberIDKey).(string) if !ok { http.Error(w, "Subscriber ID not found", http.StatusInternalServerError) @@ -121,7 +121,7 @@ func TestNewUUIDSetterErrorCases(t *testing.T) { { name: "Missing context key", config: &Config{ - CheckKeys: []string{"transaction_id"}, + ContextKeys: []string{"transaction_id"}, }, requestBody: map[string]any{ "otherKey": "value", @@ -131,7 +131,7 @@ func TestNewUUIDSetterErrorCases(t *testing.T) { { name: "Invalid context type", config: &Config{ - CheckKeys: []string{"transaction_id"}, + ContextKeys: []string{"transaction_id"}, }, requestBody: map[string]any{ "context": "not-a-map", @@ -175,4 +175,4 @@ func TestNewUUIDSetterErrorCases(t *testing.T) { } }) } -} \ No newline at end of file +} From 9f7926289443477cb5e391ec181e2b57e15d7ac6 Mon Sep 17 00:00:00 2001 From: "mayur.popli" Date: Fri, 28 Mar 2025 16:14:06 +0530 Subject: [PATCH 20/21] fix: resolved comments --- .../implementation/requestPreProcessor/cmd/plugin_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/plugin/implementation/requestPreProcessor/cmd/plugin_test.go b/pkg/plugin/implementation/requestPreProcessor/cmd/plugin_test.go index 8b4cfe9..0890dbc 100644 --- a/pkg/plugin/implementation/requestPreProcessor/cmd/plugin_test.go +++ b/pkg/plugin/implementation/requestPreProcessor/cmd/plugin_test.go @@ -34,7 +34,7 @@ func TestProviderNew(t *testing.T) { { name: "With Check Keys", config: map[string]string{ - "CheckKeys": "message_id,transaction_id", + "ContextKeys": "message_id,transaction_id", }, expectedError: false, expectedStatus: http.StatusOK, From a549323dd50dcf6e7cb6d47d277b64a9168fd808 Mon Sep 17 00:00:00 2001 From: tanyamadaan Date: Fri, 28 Mar 2025 17:01:47 +0530 Subject: [PATCH 21/21] yaml format changes --- .../implementation/router/cmd/plugin_test.go | 48 +++++-------------- .../router/testData/bap_caller.yaml | 20 ++++---- .../router/testData/bap_receiver.yaml | 16 +++---- .../router/testData/bpp_caller.yaml | 12 ++--- .../router/testData/bpp_receiver.yaml | 24 +++++----- 5 files changed, 49 insertions(+), 71 deletions(-) diff --git a/pkg/plugin/implementation/router/cmd/plugin_test.go b/pkg/plugin/implementation/router/cmd/plugin_test.go index 08c9bd7..c5ffe8e 100644 --- a/pkg/plugin/implementation/router/cmd/plugin_test.go +++ b/pkg/plugin/implementation/router/cmd/plugin_test.go @@ -32,44 +32,21 @@ func setupTestConfig(t *testing.T) string { return tempPath } -// TestRouterProviderSuccess tests the RouterProvider implementation for success cases. +// TestRouterProviderSuccess tests successful router creation. func TestRouterProviderSuccess(t *testing.T) { rulesFilePath := setupTestConfig(t) defer os.RemoveAll(filepath.Dir(rulesFilePath)) - // Define test cases - tests := []struct { - name string - ctx context.Context - config map[string]string - wantErr bool - }{ - { - name: "Valid configuration", - ctx: context.Background(), - config: map[string]string{ - "routingConfig": rulesFilePath, - }, - wantErr: false, - }, + provider := RouterProvider{} + router, _, err := provider.New(context.Background(), map[string]string{ + "routingConfig": rulesFilePath, + }) + + if err != nil { + t.Fatalf("New() unexpected error: %v", err) } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - provider := RouterProvider{} - router, _, err := provider.New(tt.ctx, tt.config) - - // Ensure no error occurred - if (err != nil) != tt.wantErr { - t.Errorf("New(%v, %v) error = %v, wantErr %v", tt.ctx, tt.config, err, tt.wantErr) - return - } - - // Ensure the router and close function are not nil - if router == nil { - t.Errorf("New(%v, %v) = nil router, want non-nil", tt.ctx, tt.config) - } - }) + if router == nil { + t.Error("New() returned nil router, want non-nil") } } @@ -114,8 +91,9 @@ func TestRouterProviderFailure(t *testing.T) { // Check for expected error if err == nil { - t.Errorf("New(%v, %v) = nil error, want error containing %q", tt.ctx, tt.config, tt.wantErr) - } else if !strings.Contains(err.Error(), tt.wantErr) { + t.Fatalf("New(%v, %v) = nil error, want error containing %q", tt.ctx, tt.config, tt.wantErr) + } + if !strings.Contains(err.Error(), tt.wantErr) { t.Errorf("New(%v, %v) = %v, want error containing %q", tt.ctx, tt.config, err, tt.wantErr) } }) diff --git a/pkg/plugin/implementation/router/testData/bap_caller.yaml b/pkg/plugin/implementation/router/testData/bap_caller.yaml index 0c595a6..6a40a0f 100644 --- a/pkg/plugin/implementation/router/testData/bap_caller.yaml +++ b/pkg/plugin/implementation/router/testData/bap_caller.yaml @@ -1,23 +1,23 @@ routingRules: - - domain: "ONDC:TRV10" - version: "2.0.0" - targetType: "bpp" + - domain: ONDC:TRV10 + version: 2.0.0 + targetType: bpp target: - url: "https://gateway.example.com" + url: https://gateway.example.com endpoints: - search - - domain: "ONDC:TRV10" - version: "2.0.0" - targetType: "bpp" + - domain: ONDC:TRV10 + version: 2.0.0 + targetType: bpp endpoints: - select - init - confirm - status - cancel - - domain: "ONDC:TRV12" - version: "2.0.0" - targetType: "bpp" + - domain: ONDC:TRV12 + version: 2.0.0 + targetType: bpp endpoints: - select - init diff --git a/pkg/plugin/implementation/router/testData/bap_receiver.yaml b/pkg/plugin/implementation/router/testData/bap_receiver.yaml index af98401..17432db 100644 --- a/pkg/plugin/implementation/router/testData/bap_receiver.yaml +++ b/pkg/plugin/implementation/router/testData/bap_receiver.yaml @@ -1,9 +1,9 @@ routingRules: - - domain: "ONDC:TRV10" - version: "2.0.0" - targetType: "url" + - domain: ONDC:TRV10 + version: 2.0.0 + targetType: url target: - url: "https://services-backend/trv/v1" + url: https://services-backend/trv/v1 endpoints: - on_select - on_init @@ -11,10 +11,10 @@ routingRules: - on_status - on_update - on_cancel - - domain: "ONDC:TRV10" - version: "2.0.0" - targetType: "publisher" + - domain: ONDC:TRV10 + version: 2.0.0 + targetType: publisher target: - publisherId: "trv_topic_id1" + publisherId: 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 index 8d0c212..339b92d 100644 --- a/pkg/plugin/implementation/router/testData/bpp_caller.yaml +++ b/pkg/plugin/implementation/router/testData/bpp_caller.yaml @@ -1,7 +1,7 @@ routingRules: - - domain: "ONDC:TRV10" - version: "2.0.0" - targetType: "bap" + - domain: ONDC:TRV10 + version: 2.0.0 + targetType: bap endpoints: - on_search - on_select @@ -10,9 +10,9 @@ routingRules: - on_status - on_update - on_cancel - - domain: "ONDC:TRV11" - version: "2.0.0" - targetType: "bap" + - domain: ONDC:TRV11 + version: 2.0.0 + targetType: bap endpoints: - on_search - on_select diff --git a/pkg/plugin/implementation/router/testData/bpp_receiver.yaml b/pkg/plugin/implementation/router/testData/bpp_receiver.yaml index a8f668e..7355592 100644 --- a/pkg/plugin/implementation/router/testData/bpp_receiver.yaml +++ b/pkg/plugin/implementation/router/testData/bpp_receiver.yaml @@ -1,27 +1,27 @@ routingRules: - - domain: "ONDC:TRV10" - version: "2.0.0" - targetType: "url" + - domain: ONDC:TRV10 + version: 2.0.0 + targetType: url target: - url: "https://services-backend/trv/v1" + url: https://services-backend/trv/v1 endpoints: - select - init - confirm - status - cancel - - domain: "ONDC:TRV10" - version: "2.0.0" - targetType: "publisher" + - domain: ONDC:TRV10 + version: 2.0.0 + targetType: publisher target: - publisherId: "trv_topic_id1" + publisherId: trv_topic_id1 endpoints: - search - - domain: "ONDC:TRV11" - version: "2.0.0" - targetType: "url" + - domain: ONDC:TRV11 + version: 2.0.0 + targetType: url target: - url: "https://services-backend/trv/v1" + url: https://services-backend/trv/v1 endpoints: - select - init