Skip to content

Commit

Permalink
feat: dummypay connector tests
Browse files Browse the repository at this point in the history
Signed-off-by: Lawrence Zawila <113581282+darkmatterpool@users.noreply.github.com>
  • Loading branch information
darkmatterpool committed Sep 25, 2022
1 parent 6a0f52f commit c862071
Show file tree
Hide file tree
Showing 14 changed files with 338 additions and 89 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ require (
github.com/pkg/errors v0.9.1
github.com/rs/cors v1.8.2
github.com/sirupsen/logrus v1.9.0
github.com/spf13/afero v1.9.2
github.com/spf13/cobra v1.5.0
github.com/spf13/viper v1.12.0
github.com/stretchr/testify v1.8.0
Expand Down Expand Up @@ -89,7 +90,6 @@ require (
github.com/pierrec/lz4/v4 v4.1.15 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/spf13/afero v1.9.2 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
Expand Down
57 changes: 57 additions & 0 deletions pkg/bridge/connectors/dummypay/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package dummypay

import (
"os"
"testing"
"time"

"github.com/stretchr/testify/assert"
)

// TestConfigString tests the string representation of the config.
func TestConfigString(t *testing.T) {
t.Parallel()

config := Config{
Directory: "test",
FilePollingPeriod: time.Second,
FileGenerationPeriod: time.Minute,
}

assert.Equal(t, "directory: test, filePollingPeriod: 1s, fileGenerationPeriod: 1m0s", config.String())
}

// TestConfigValidate tests the validation of the config.
func TestConfigValidate(t *testing.T) {
t.Parallel()

var config Config

// fail on missing directory
assert.EqualError(t, config.Validate(), ErrMissingDirectory.Error())

// fail on missing RW access to directory
config.Directory = "/non-existing"
assert.Error(t, config.Validate())

// set directory with RW access
userHomeDir, err := os.UserHomeDir()
if err != nil {
t.Error(err)
}

config.Directory = userHomeDir

// fail on invalid file polling period
config.FilePollingPeriod = -1
assert.ErrorIs(t, config.Validate(), ErrFilePollingPeriodInvalid)

// fail on invalid file generation period
config.FilePollingPeriod = 1
config.FileGenerationPeriod = -1
assert.ErrorIs(t, config.Validate(), ErrFileGenerationPeriodInvalid)

// success
config.FileGenerationPeriod = 1
assert.NoError(t, config.Validate())
}
8 changes: 5 additions & 3 deletions pkg/bridge/connectors/dummypay/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const connectorName = "dummypay"
type Connector struct {
logger sharedlogging.Logger
cfg Config
fs fs
}

// Install executes post-installation steps to read and generate files.
Expand All @@ -36,7 +37,7 @@ func (c *Connector) Install(ctx task.ConnectorContext[TaskDescriptor]) error {
func (c *Connector) Uninstall(ctx context.Context) error {
c.logger.Infof("Removing generated files from '%s'...", c.cfg.Directory)

err := removeFiles(c.cfg)
err := removeFiles(c.cfg, c.fs)
if err != nil {
return fmt.Errorf("failed to remove generated files: %w", err)
}
Expand All @@ -48,15 +49,16 @@ func (c *Connector) Uninstall(ctx context.Context) error {
func (c *Connector) Resolve(descriptor TaskDescriptor) task.Task {
c.logger.Infof("Executing '%s' task...", descriptor.Key)

return handleResolve(c.cfg, descriptor)
return handleResolve(c.cfg, descriptor, c.fs)
}

// NewConnector creates a new dummy payment connector.
func NewConnector(logger sharedlogging.Logger, cfg Config) *Connector {
func NewConnector(logger sharedlogging.Logger, cfg Config, fs fs) *Connector {
return &Connector{
logger: logger.WithFields(map[string]any{
"component": "connector",
}),
cfg: cfg,
fs: fs,
}
}
69 changes: 69 additions & 0 deletions pkg/bridge/connectors/dummypay/connector_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package dummypay

import (
"context"
"reflect"
"testing"

"github.com/numary/go-libs/sharedlogging"
payments "github.com/numary/payments/pkg"
"github.com/numary/payments/pkg/bridge/task"
"github.com/stretchr/testify/assert"
)

// Create a minimal mock for connector installation
type (
mockConnectorContext[TaskDescriptor payments.TaskDescriptor] struct {
ctx context.Context
}
mockScheduler[TaskDescriptor payments.TaskDescriptor] struct{}
)

func (mcc *mockConnectorContext[TaskDescriptor]) Context() context.Context {
return mcc.ctx
}

func (s mockScheduler[TaskDescriptor]) Schedule(p TaskDescriptor, restart bool) error {
return nil
}

func (mcc *mockConnectorContext[TaskDescriptor]) Scheduler() task.Scheduler[TaskDescriptor] {
return mockScheduler[TaskDescriptor]{}
}

func TestConnector(t *testing.T) {
t.Parallel()

config := Config{}
logger := sharedlogging.GetLogger(context.Background())

fs := newTestFS()

connector := NewConnector(logger, config, fs)

err := connector.Install(new(mockConnectorContext[TaskDescriptor]))
assert.NoErrorf(t, err, "Install() failed: %v")

testCases := []struct {
key taskKey
task task.Task
}{
{taskKeyReadFiles, taskReadFiles(config, fs)},
{taskKeyGenerateFiles, taskGenerateFiles(config, fs)},
{taskKeyIngest, taskIngest(config, TaskDescriptor{}, fs)},
}

for _, testCase := range testCases {
assert.EqualValues(t,
reflect.ValueOf(testCase.task).String(),
reflect.ValueOf(connector.Resolve(TaskDescriptor{Key: testCase.key})).String(),
)
}

assert.EqualValues(t,
reflect.ValueOf(func() error { return nil }).String(),
reflect.ValueOf(connector.Resolve(TaskDescriptor{Key: "test"})).String(),
)

assert.NoError(t, connector.Uninstall(context.Background()))
}
12 changes: 12 additions & 0 deletions pkg/bridge/connectors/dummypay/fs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package dummypay

import (
"github.com/spf13/afero"
)

type fs afero.Fs

// newFS creates a new file system access point.
func newFS() fs {
return afero.NewOsFs()
}
10 changes: 10 additions & 0 deletions pkg/bridge/connectors/dummypay/fs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package dummypay

import "github.com/spf13/afero"

func newTestFS() fs {
fs := newFS()
fs = afero.NewMemMapFs()

return fs
}
2 changes: 1 addition & 1 deletion pkg/bridge/connectors/dummypay/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func (l *loader) ApplyDefaults(cfg Config) Config {

// Load returns the connector.
func (l *loader) Load(logger sharedlogging.Logger, config Config) integration.Connector[TaskDescriptor] {
return NewConnector(logger, config)
return NewConnector(logger, config, newFS())
}

// NewLoader creates a new loader.
Expand Down
29 changes: 29 additions & 0 deletions pkg/bridge/connectors/dummypay/loader_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package dummypay

import (
"context"
"testing"
"time"

"github.com/numary/go-libs/sharedlogging"
"github.com/stretchr/testify/assert"
)

// TestLoader tests the loader.
func TestLoader(t *testing.T) {
t.Parallel()

config := Config{}
logger := sharedlogging.GetLogger(context.Background())

loader := NewLoader()

assert.Equal(t, connectorName, loader.Name())
assert.Equal(t, 10, loader.AllowTasks())
assert.Equal(t, Config{
FilePollingPeriod: 10 * time.Second,
FileGenerationPeriod: 5 * time.Second,
}, loader.ApplyDefaults(config))

assert.EqualValues(t, NewConnector(logger, config, newFS()), loader.Load(logger, config))
}
9 changes: 5 additions & 4 deletions pkg/bridge/connectors/dummypay/remove_files.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ package dummypay

import (
"fmt"
"os"
"strings"

"github.com/spf13/afero"
)

// removeFiles removes all files from the given directory.
// Only removes files that has generatedFilePrefix in the name.
func removeFiles(config Config) error {
dir, err := os.ReadDir(config.Directory)
func removeFiles(config Config, fs fs) error {
dir, err := afero.ReadDir(fs, config.Directory)
if err != nil {
return fmt.Errorf("failed to open directory '%s': %w", config.Directory, err)
}
Expand All @@ -22,7 +23,7 @@ func removeFiles(config Config) error {
}

// remove the file
err = os.Remove(fmt.Sprintf("%s/%s", config.Directory, file.Name()))
err = fs.Remove(fmt.Sprintf("%s/%s", config.Directory, file.Name()))
if err != nil {
return fmt.Errorf("failed to remove file '%s': %w", file.Name(), err)
}
Expand Down
8 changes: 4 additions & 4 deletions pkg/bridge/connectors/dummypay/task_descriptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ type TaskDescriptor struct {
}

// handleResolve resolves a task execution request based on the task descriptor.
func handleResolve(config Config, descriptor TaskDescriptor) task.Task {
func handleResolve(config Config, descriptor TaskDescriptor, fs fs) task.Task {
switch descriptor.Key {
case taskKeyReadFiles:
return taskReadFiles(config)
return taskReadFiles(config, fs)
case taskKeyIngest:
return taskIngest(config, descriptor)
return taskIngest(config, descriptor, fs)
case taskKeyGenerateFiles:
return taskGenerateFiles(config)
return taskGenerateFiles(config, fs)
}

// This should never happen.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ import (
"encoding/json"
"fmt"
"math/rand"
"os"
"time"

"github.com/numary/go-libs/sharedlogging"
payments "github.com/numary/payments/pkg"
"github.com/numary/payments/pkg/bridge/task"
)
Expand All @@ -27,49 +25,57 @@ func newTaskGenerateFiles() TaskDescriptor {
}

// taskGenerateFiles generates payment files to a given directory.
func taskGenerateFiles(config Config) task.Task {
return func(ctx context.Context, logger sharedlogging.Logger,
scheduler task.Scheduler[TaskDescriptor]) error {
func taskGenerateFiles(config Config, fs fs) task.Task {
return func(ctx context.Context) error {
for {
select {
case <-ctx.Done():
return nil
case <-time.After(config.FileGenerationPeriod):
key := fmt.Sprintf("%s-%d", generatedFilePrefix, time.Now().UnixNano())
fileKey := fmt.Sprintf("%s/%s.json", config.Directory, key)

var paymentObj payment

// Generate a random payment.
paymentObj.Reference = key
paymentObj.Type = generateRandomType()
paymentObj.Status = generateRandomStatus()
paymentObj.InitialAmount = int64(generateRandomNumber())
paymentObj.Asset = asset

file, err := os.Create(fileKey)
err := generateFile(config, fs)
if err != nil {
return fmt.Errorf("failed to create file: %w", err)
return err
}
}
}
}
}

// Encode the payment object as JSON to a new file.
err = json.NewEncoder(file).Encode(&paymentObj)
if err != nil {
// Close the file before returning.
if fileCloseErr := file.Close(); fileCloseErr != nil {
return fmt.Errorf("failed to close file: %w", fileCloseErr)
}
func generateFile(config Config, fs fs) error {
key := fmt.Sprintf("%s-%d", generatedFilePrefix, time.Now().UnixNano())
fileKey := fmt.Sprintf("%s/%s.json", config.Directory, key)

return fmt.Errorf("failed to encode json into file: %w", err)
}
var paymentObj payment

// Close the file.
if err = file.Close(); err != nil {
return fmt.Errorf("failed to close file: %w", err)
}
}
// Generate a random payment.
paymentObj.Reference = key
paymentObj.Type = generateRandomType()
paymentObj.Status = generateRandomStatus()
paymentObj.InitialAmount = int64(generateRandomNumber())
paymentObj.Asset = asset

file, err := fs.Create(fileKey)
if err != nil {
return fmt.Errorf("failed to create file: %w", err)
}

// Encode the payment object as JSON to a new file.
err = json.NewEncoder(file).Encode(&paymentObj)
if err != nil {
// Close the file before returning.
if fileCloseErr := file.Close(); fileCloseErr != nil {
return fmt.Errorf("failed to close file: %w", fileCloseErr)
}

return fmt.Errorf("failed to encode json into file: %w", err)
}

// Close the file.
if err = file.Close(); err != nil {
return fmt.Errorf("failed to close file: %w", err)
}

return nil
}

// nMax is the maximum number that can be generated
Expand Down
Loading

0 comments on commit c862071

Please sign in to comment.