diff --git a/coverage.out b/coverage.out deleted file mode 100644 index e93e451..0000000 --- a/coverage.out +++ /dev/null @@ -1,13 +0,0 @@ -mode: set -github.com/beckn/beckn-onix/shared/plugin/implementation/publisher/cmd/plugin.go:12.74,13.19 1 1 -github.com/beckn/beckn-onix/shared/plugin/implementation/publisher/cmd/plugin.go:13.19,15.3 1 1 -github.com/beckn/beckn-onix/shared/plugin/implementation/publisher/cmd/plugin.go:17.2,18.26 2 1 -github.com/beckn/beckn-onix/shared/plugin/implementation/publisher/cmd/plugin.go:18.26,20.3 1 1 -github.com/beckn/beckn-onix/shared/plugin/implementation/publisher/cmd/plugin.go:22.2,23.24 2 1 -github.com/beckn/beckn-onix/shared/plugin/implementation/publisher/cmd/plugin.go:23.24,25.3 1 1 -github.com/beckn/beckn-onix/shared/plugin/implementation/publisher/cmd/plugin.go:27.2,30.8 1 1 -github.com/beckn/beckn-onix/shared/plugin/implementation/publisher/cmd/plugin.go:37.109,38.16 1 1 -github.com/beckn/beckn-onix/shared/plugin/implementation/publisher/cmd/plugin.go:38.16,40.3 1 1 -github.com/beckn/beckn-onix/shared/plugin/implementation/publisher/cmd/plugin.go:42.2,43.16 2 1 -github.com/beckn/beckn-onix/shared/plugin/implementation/publisher/cmd/plugin.go:43.16,45.3 1 1 -github.com/beckn/beckn-onix/shared/plugin/implementation/publisher/cmd/plugin.go:47.2,47.32 1 0 diff --git a/shared/plugin/definition/publisher.go b/pkg/plugin/definition/publisher.go similarity index 87% rename from shared/plugin/definition/publisher.go rename to pkg/plugin/definition/publisher.go index 737aff7..93f9e21 100644 --- a/shared/plugin/definition/publisher.go +++ b/pkg/plugin/definition/publisher.go @@ -7,10 +7,10 @@ type Publisher interface { // Publish sends a message (as a byte slice) using the underlying messaging system. Publish(ctx context.Context, msg []byte) error - Close() error // Important for releasing resources + Close() error // Important for releasing resources. } type PublisherProvider interface { - // New initializes a new publisher instance with the given configuration + // New initializes a new publisher instance with the given configuration. New(ctx context.Context, config map[string]string) (Publisher, error) } diff --git a/shared/plugin/definition/signVerifier.go b/pkg/plugin/definition/signVerifier.go similarity index 100% rename from shared/plugin/definition/signVerifier.go rename to pkg/plugin/definition/signVerifier.go diff --git a/shared/plugin/definition/signer.go b/pkg/plugin/definition/signer.go similarity index 100% rename from shared/plugin/definition/signer.go rename to pkg/plugin/definition/signer.go diff --git a/pkg/plugin/implementation/publisher/cmd/plugin.go b/pkg/plugin/implementation/publisher/cmd/plugin.go new file mode 100644 index 0000000..561d6f2 --- /dev/null +++ b/pkg/plugin/implementation/publisher/cmd/plugin.go @@ -0,0 +1,51 @@ +package main + +import ( + "context" + "fmt" + + "github.com/beckn/beckn-onix/pkg/plugin/definition" + "github.com/beckn/beckn-onix/pkg/plugin/implementation/publisher" +) + +// Returns error if required fields are missing. +func validateConfig(config map[string]string) (*publisher.Config, error) { + if config == nil { + return nil, fmt.Errorf("config cannot be nil") + } + + project, ok := config["project"] + if !ok || project == "" { + return nil, fmt.Errorf("project ID is required") + } + + topic, ok := config["topic"] + if !ok || topic == "" { + return nil, fmt.Errorf("topic ID is required") + } + + return &publisher.Config{ + ProjectID: project, + TopicID: topic, + }, nil +} + +// PublisherProvider implements the definition.PublisherProvider interface. +type PublisherProvider struct{} + +// New creates a new Publisher instance. +func (p PublisherProvider) New(ctx context.Context, config map[string]string) (definition.Publisher, error) { + if ctx == nil { + return nil, fmt.Errorf("context cannot be nil") + } + + cfg, err := validateConfig(config) + if err != nil { + return nil, fmt.Errorf("invalid config: %w", err) + } + + return publisher.New(ctx, cfg) +} + +// Provider is the exported symbol that the plugin manager will look for. +var Provider definition.PublisherProvider = PublisherProvider{} diff --git a/pkg/plugin/implementation/publisher/cmd/plugin_test.go b/pkg/plugin/implementation/publisher/cmd/plugin_test.go new file mode 100644 index 0000000..4c40e54 --- /dev/null +++ b/pkg/plugin/implementation/publisher/cmd/plugin_test.go @@ -0,0 +1,74 @@ +package main + +import ( + "context" + "testing" + + "github.com/beckn/beckn-onix/pkg/plugin/definition" +) + +// MockPublisher is a mock implementation of the definition.Publisher interface for testing. +type MockPublisher struct{} + +func (m *MockPublisher) Publish(ctx context.Context, msg []byte) error { + return nil +} + +func (m *MockPublisher) Close() error { + return nil +} + +// TestValidateConfig tests the validateConfig function. +func TestValidateConfig(t *testing.T) { + tests := []struct { + name string + config map[string]string + wantErr bool + }{ + {"Valid config", map[string]string{"project": "test-project", "topic": "test-topic"}, false}, + {"Nil config", nil, true}, + {"Missing project", map[string]string{"topic": "test-topic"}, true}, + {"Missing topic", map[string]string{"project": "test-project"}, true}, + {"Empty project", map[string]string{"project": "", "topic": "test-topic"}, true}, + {"Empty topic", map[string]string{"project": "test-project", "topic": ""}, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := validateConfig(tt.config) + if (err != nil) != tt.wantErr { + t.Errorf("validateConfig() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +// TestPublisherProviderNew tests the New method of PublisherProvider. +func TestPublisherProviderNew(t *testing.T) { + tests := []struct { + name string + ctx context.Context + config map[string]string + wantErr bool + }{ + {"Nil context", nil, map[string]string{"project": "test-project", "topic": "test-topic"}, true}, + {"Invalid config", context.Background(), map[string]string{"project": "", "topic": "test-topic"}, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + provider := PublisherProvider{} + _, err := provider.New(tt.ctx, tt.config) + if (err != nil) != tt.wantErr { + t.Errorf("PublisherProvider.New() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +// TestProviderImplementation verifies that the Provider is correctly initialized. +func TestProviderImplementation(t *testing.T) { + if _, ok := interface{}(Provider).(definition.PublisherProvider); !ok { + t.Errorf("Provider does not implement definition.PublisherProvider") + } +} diff --git a/pkg/plugin/implementation/publisher/publisher.go b/pkg/plugin/implementation/publisher/publisher.go new file mode 100644 index 0000000..245a120 --- /dev/null +++ b/pkg/plugin/implementation/publisher/publisher.go @@ -0,0 +1,95 @@ +package publisher + +import ( + "context" + "errors" + "fmt" + "log" + "strings" + + "cloud.google.com/go/pubsub" + "google.golang.org/api/option" +) + +// Config holds the Pub/Sub configuration. +type Config struct { + ProjectID string + TopicID string +} + +// Publisher is a concrete implementation of a Google Cloud Pub/Sub publisher. +type Publisher struct { + client *pubsub.Client + topic *pubsub.Topic + config *Config +} + +var ( + ErrProjectMissing = errors.New("missing required field 'Project'") + ErrTopicMissing = errors.New("missing required field 'Topic'") + ErrEmptyConfig = errors.New("empty config") +) + + +func validate(cfg *Config) error { + if cfg == nil { + return ErrEmptyConfig + } + if strings.TrimSpace(cfg.ProjectID) == "" { + return ErrProjectMissing + } + if strings.TrimSpace(cfg.TopicID) == "" { + return ErrTopicMissing + } + return nil +} + +// New initializes a new Publisher instance. +// It creates a real pubsub.Client and then calls NewWithClient. +func New(ctx context.Context, config *Config, opts ...option.ClientOption) (*Publisher, error) { + if err := validate(config); err != nil { + return nil, err + } + client, err := pubsub.NewClient(ctx, config.ProjectID, opts...) + if err != nil { + return nil, fmt.Errorf("failed to create pubsub client: %w", err) + } + + topic := client.Topic(config.TopicID) + exists, err := topic.Exists(ctx) + if err != nil { + _ = client.Close() + return nil, fmt.Errorf("failed to check topic existence: %w", err) + } + if !exists { + _ = client.Close() + return nil, fmt.Errorf("topic %s does not exist", config.TopicID) + } + return &Publisher{ + client: client, + topic: topic, + config: config, + }, nil +} + + +// Publish sends a message to Google Cloud Pub/Sub. +func (p *Publisher) Publish(ctx context.Context, msg []byte) error { + pubsubMsg := &pubsub.Message{ + Data: msg, + } + + result := p.topic.Publish(ctx, pubsubMsg) + id, err := result.Get(ctx) + if err != nil { + return fmt.Errorf("failed to publish message: %w", err) + } + + log.Printf("Published message with ID: %s\n", id) + return nil +} + +// Close closes the underlying Pub/Sub client. +func (p *Publisher) Close() error { + return p.client.Close() +} diff --git a/pkg/plugin/implementation/publisher/publisher_test.go b/pkg/plugin/implementation/publisher/publisher_test.go new file mode 100644 index 0000000..9ca3d2c --- /dev/null +++ b/pkg/plugin/implementation/publisher/publisher_test.go @@ -0,0 +1,98 @@ +package publisher + +import ( + "context" + "errors" + "testing" +) + +// TestValidate tests the validate function. +func TestValidate(t *testing.T) { + tests := []struct { + name string + config *Config + wantErr error + }{ + { + name: "Valid config", + config: &Config{ProjectID: "test-project", TopicID: "test-topic"}, + wantErr: nil, + }, + { + name: "Nil config", + config: nil, + wantErr: ErrEmptyConfig, + }, + { + name: "Empty project ID", + config: &Config{ProjectID: "", TopicID: "test-topic"}, + wantErr: ErrProjectMissing, + }, + { + name: "Whitespace project ID", + config: &Config{ProjectID: " ", TopicID: "test-topic"}, + wantErr: ErrProjectMissing, + }, + { + name: "Empty topic ID", + config: &Config{ProjectID: "test-project", TopicID: ""}, + wantErr: ErrTopicMissing, + }, + { + name: "Whitespace topic ID", + config: &Config{ProjectID: "test-project", TopicID: " "}, + wantErr: ErrTopicMissing, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validate(tt.config) + if !errors.Is(err, tt.wantErr) { + t.Errorf("validate() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +// TestNew tests the New function with validation errors only. +// We can't easily test the pubsub client creation parts without complex mocks. +func TestNew(t *testing.T) { + tests := []struct { + name string + ctx context.Context + config *Config + wantErr bool + }{ + { + // Should fail validation + name: "Empty project ID", + ctx: context.Background(), + config: &Config{ProjectID: "", TopicID: "test-topic"}, + wantErr: true, + }, + { + // Should fail validation + name: "Empty topic ID", + ctx: context.Background(), + config: &Config{ProjectID: "test-project", TopicID: ""}, + wantErr: true, + }, + { + // Should fail due to nil context + name: "Nil context", + ctx: nil, + config: &Config{ProjectID: "test-project", TopicID: "test-topic"}, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := New(tt.ctx, tt.config) + if (err != nil) != tt.wantErr { + t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/shared/plugin/implementation/signVerifier/cmd/plugin.go b/pkg/plugin/implementation/signVerifier/cmd/plugin.go similarity index 66% rename from shared/plugin/implementation/signVerifier/cmd/plugin.go rename to pkg/plugin/implementation/signVerifier/cmd/plugin.go index 1e4fb06..35c1287 100644 --- a/shared/plugin/implementation/signVerifier/cmd/plugin.go +++ b/pkg/plugin/implementation/signVerifier/cmd/plugin.go @@ -4,17 +4,16 @@ import ( "context" "errors" - "github.com/beckn/beckn-onix/shared/plugin/definition" + "github.com/beckn/beckn-onix/pkg/plugin/definition" - plugin "github.com/beckn/beckn-onix/shared/plugin/definition" - verifier "github.com/beckn/beckn-onix/shared/plugin/implementation/signVerifier" + verifier "github.com/beckn/beckn-onix/pkg/plugin/implementation/signVerifier" ) // VerifierProvider provides instances of Verifier. type VerifierProvider struct{} // New initializes a new Verifier instance. -func (vp VerifierProvider) New(ctx context.Context, config map[string]string) (plugin.Verifier, func() error, error) { +func (vp VerifierProvider) New(ctx context.Context, config map[string]string) (definition.Verifier, func() error, error) { if ctx == nil { return nil, nil, errors.New("context cannot be nil") } diff --git a/shared/plugin/implementation/signVerifier/cmd/plugin_test.go b/pkg/plugin/implementation/signVerifier/cmd/plugin_test.go similarity index 100% rename from shared/plugin/implementation/signVerifier/cmd/plugin_test.go rename to pkg/plugin/implementation/signVerifier/cmd/plugin_test.go diff --git a/shared/plugin/implementation/signVerifier/signVerifier.go b/pkg/plugin/implementation/signVerifier/signVerifier.go similarity index 100% rename from shared/plugin/implementation/signVerifier/signVerifier.go rename to pkg/plugin/implementation/signVerifier/signVerifier.go diff --git a/shared/plugin/implementation/signVerifier/signVerifier_test.go b/pkg/plugin/implementation/signVerifier/signVerifier_test.go similarity index 100% rename from shared/plugin/implementation/signVerifier/signVerifier_test.go rename to pkg/plugin/implementation/signVerifier/signVerifier_test.go diff --git a/shared/plugin/implementation/signer/cmd/plugin.go b/pkg/plugin/implementation/signer/cmd/plugin.go similarity index 82% rename from shared/plugin/implementation/signer/cmd/plugin.go rename to pkg/plugin/implementation/signer/cmd/plugin.go index 854ecbe..2d78d98 100644 --- a/shared/plugin/implementation/signer/cmd/plugin.go +++ b/pkg/plugin/implementation/signer/cmd/plugin.go @@ -4,8 +4,8 @@ import ( "context" "errors" - "github.com/beckn/beckn-onix/shared/plugin/definition" - "github.com/beckn/beckn-onix/shared/plugin/implementation/signer" + "github.com/beckn/beckn-onix/pkg/plugin/definition" + "github.com/beckn/beckn-onix/pkg/plugin/implementation/signer" ) // SignerProvider implements the definition.SignerProvider interface. diff --git a/shared/plugin/implementation/signer/cmd/plugin_test.go b/pkg/plugin/implementation/signer/cmd/plugin_test.go similarity index 100% rename from shared/plugin/implementation/signer/cmd/plugin_test.go rename to pkg/plugin/implementation/signer/cmd/plugin_test.go diff --git a/shared/plugin/implementation/signer/signer.go b/pkg/plugin/implementation/signer/signer.go similarity index 100% rename from shared/plugin/implementation/signer/signer.go rename to pkg/plugin/implementation/signer/signer.go diff --git a/shared/plugin/implementation/signer/signer_test.go b/pkg/plugin/implementation/signer/signer_test.go similarity index 100% rename from shared/plugin/implementation/signer/signer_test.go rename to pkg/plugin/implementation/signer/signer_test.go diff --git a/shared/plugin/manager.go b/pkg/plugin/manager.go similarity index 98% rename from shared/plugin/manager.go rename to pkg/plugin/manager.go index 5fed0f1..60d18ac 100644 --- a/shared/plugin/manager.go +++ b/pkg/plugin/manager.go @@ -7,7 +7,7 @@ import ( "plugin" "strings" - "github.com/beckn/beckn-onix/shared/plugin/definition" + "github.com/beckn/beckn-onix/pkg/plugin/definition" ) // Config represents the plugin manager configuration.