From ba791435b7f6adb619b18f086a098ae9fc6c32d5 Mon Sep 17 00:00:00 2001 From: Julien Riou Date: Thu, 20 May 2021 18:03:45 +0200 Subject: [PATCH] feat: add filter on price range WIP Signed-off-by: Julien Riou --- filter_range.go | 48 ++++++++++++++++++++++++++++++++++++++++++++ filter_range_test.go | 45 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 filter_range.go create mode 100644 filter_range_test.go diff --git a/filter_range.go b/filter_range.go new file mode 100644 index 0000000..57917d6 --- /dev/null +++ b/filter_range.go @@ -0,0 +1,48 @@ +package main + +import ( + "regexp" + + log "github.com/sirupsen/logrus" +) + +// RangeFilter to store the pattern to match product model and price limits +type RangeFilter struct { + model *regexp.Regexp + min float64 + max float64 +} + +// NewRangeFilter to create a RangeFilter +func NewRangeFilter(regex string, min float64, max float64) (*RangeFilter, error) { + var err error + var compiledRegex *regexp.Regexp + + log.Debugf("compiling model filter regex") + if regex != "" { + compiledRegex, err = regexp.Compile(regex) + if err != nil { + return nil, err + } + } + + return &RangeFilter{ + model: compiledRegex, + min: min, + max: max, + }, nil +} + +// Include returns false when a product name matches the model regex and price is outside of the range +// implements the Filter interface +func (f *RangeFilter) Include(product *Product) bool { + if f.model == nil { + return true + } + if f.model.MatchString(product.Name) && product.Price < f.min || product.Price > f.max { + log.Debugf("product %s excluded because price for the model is outside of the range [%.2f-%.2f]", product.Name, f.min, f.max) + return false + } + log.Debugf("product %s included because price range filter is not applicable", product.Name) + return true +} diff --git a/filter_range_test.go b/filter_range_test.go new file mode 100644 index 0000000..d9a9f73 --- /dev/null +++ b/filter_range_test.go @@ -0,0 +1,45 @@ +package main + +import ( + "fmt" + "testing" +) + +func TestRangeFilter(t *testing.T) { + tests := []struct { + name string // product name + price float64 // product price + model string // model regex to apply on the product name + min float64 // minimum price + max float64 // maximum price + included bool // should be included or not + }{ + {"MSI GeForce RTX 3090 GAMING X", 99.99, "3090", 50.0, 100.0, true}, // model match and price is in the range, should be included + {"MSI GeForce RTX 3090 GAMING X", 99.99, "3080", 50.0, 100.0, true}, // model doesn't match, should be included + {"MSI GeForce RTX 3090 GAMING X", 999.99, "3090", 50.0, 100.0, false}, // model match and price is outside of the range, shoud not be included + {"MSI GeForce RTX 3090 GAMING X", 99.99, "", 50.0, 100.0, true}, // model regex is missing, should be included + } + + for i, tc := range tests { + t.Run(fmt.Sprintf("TestRangeFilter#%d", i), func(t *testing.T) { + product := &Product{Name: tc.name, Price: tc.price} + filter, err := NewRangeFilter(tc.model, tc.min, tc.max) + if err != nil { + t.Errorf("cannot create filter with model regex '%s' and price range [%.2f, %.2f]: %s", tc.model, tc.min, tc.max, err) + } + + included := filter.Include(product) + + if included != tc.included { + t.Errorf("product '%s' with model regex '%s' and range [%.2f, %.2f]: got included=%t, want included=%t", tc.name, tc.model, tc.min, tc.max, included, tc.included) + } else { + if included { + t.Logf("product '%s' included by model regex '%s' and range [%.2f, %.2f]", tc.name, tc.model, tc.min, tc.max) + } else { + t.Logf("product '%s' excluded by model regex '%s' and range [%.2f, %.2f]", tc.name, tc.model, tc.min, tc.max) + } + } + + }) + } +}