Skip to content

Commit

Permalink
Add price change reporting and associated tests.
Browse files Browse the repository at this point in the history
tjhowse committed Aug 21, 2024
1 parent ffcda79 commit 90c54f8
Showing 6 changed files with 187 additions and 6 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@ go 1.22.5

require (
github.com/caarlos0/env/v11 v11.1.0
github.com/influxdata/influxdb-client-go/v2 v2.13.0
github.com/influxdata/influxdb-client-go/v2 v2.14.0
github.com/mattn/go-sqlite3 v1.14.22
github.com/shopspring/decimal v1.4.0
golang.org/x/sys v0.22.0
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -9,8 +9,8 @@ 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/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/influxdata/influxdb-client-go/v2 v2.13.0 h1:ioBbLmR5NMbAjP4UVA5r9b5xGjpABD7j65pI8kFphDM=
github.com/influxdata/influxdb-client-go/v2 v2.13.0/go.mod h1:k+spCbt9hcvqvUiz0sr5D8LolXHqAAOfPw9v/RIRHl4=
github.com/influxdata/influxdb-client-go/v2 v2.14.0 h1:AjbBfJuq+QoaXNcrova8smSjwJdUHnwvfjMF71M1iI4=
github.com/influxdata/influxdb-client-go/v2 v2.14.0/go.mod h1:Ahpm3QXKMJslpXl3IftVLVezreAUtBOTZssDrjZEFHI=
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU=
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo=
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
8 changes: 7 additions & 1 deletion influxdb.go
Original file line number Diff line number Diff line change
@@ -37,6 +37,12 @@ func (i *influxDB) Init(url, token, org, bucket string) {
}

func (i *influxDB) WriteProductDatapoint(info shared.ProductInfo) {
values := map[string]interface{}{"cents": info.PriceCents, "grams": info.WeightGrams}

// If the price has meaningfully changed, report the change
if info.PriceCents != 0 && info.PreviousPriceCents != 0 && info.PriceCents != info.PreviousPriceCents {
values["cents_change"] = info.PriceCents - info.PreviousPriceCents
}
p := influxdb2.NewPoint("product",
map[string]string{
"name": info.Name,
@@ -45,7 +51,7 @@ func (i *influxDB) WriteProductDatapoint(info shared.ProductInfo) {
"department": info.Department,
"id": info.ID,
},
map[string]interface{}{"cents": info.PriceCents, "grams": info.WeightGrams},
values,
info.Timestamp,
)
i.groceryWriteAPI.WritePoint(p)
154 changes: 154 additions & 0 deletions influxdb_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package main

import (
"testing"
"time"

"github.com/influxdata/influxdb-client-go/v2/api"
"github.com/influxdata/influxdb-client-go/v2/api/write"
shared "github.com/tjhowse/aus_grocery_price_database/internal/shared"
)

type MockInfluxdbWriteAPI struct {
writtenPoints []*write.Point
}

func (m *MockInfluxdbWriteAPI) WritePoint(p *write.Point) {
m.writtenPoints = append(m.writtenPoints, p)
}

func (m *MockInfluxdbWriteAPI) WriteRecord(line string) {}

func (m *MockInfluxdbWriteAPI) Flush() {}

func (m *MockInfluxdbWriteAPI) Errors() <-chan error {
return make(chan error)
}

// func (m *MockInfluxdbWriteAPI) Init() {
// m.writtenPoints = []*write.Point{}
// }

func (m *MockInfluxdbWriteAPI) SetWriteFailedCallback(cb api.WriteFailedCallback) {}

func InitMockInfluxDB() (*influxDB, *MockInfluxdbWriteAPI, *MockInfluxdbWriteAPI) {
i := influxDB{}
groceryMock := &MockInfluxdbWriteAPI{}
i.groceryWriteAPI = groceryMock
systemMock := &MockInfluxdbWriteAPI{}
i.systemWriteAPI = systemMock

return &i, groceryMock, systemMock
}

func TestWriteProductDatapoint(t *testing.T) {
desiredTags := map[string]string{
"name": "Test Product",
"store": "Test Store",
"location": "Test Location",
"department": "Test Department",
}
i, gMock, _ := InitMockInfluxDB()
i.WriteProductDatapoint(shared.ProductInfo{
Name: desiredTags["name"],
Store: desiredTags["store"],
Location: desiredTags["location"],
Department: desiredTags["department"],
PriceCents: 100,
PreviousPriceCents: 0,
WeightGrams: 1000,
Timestamp: time.Now(),
})
i.WriteProductDatapoint(shared.ProductInfo{
Name: desiredTags["name"],
Store: desiredTags["store"],
Location: desiredTags["location"],
Department: desiredTags["department"],
PriceCents: 101,
PreviousPriceCents: 100,
WeightGrams: 1000,
Timestamp: time.Now(),
})
i.WriteProductDatapoint(shared.ProductInfo{
Name: desiredTags["name"],
Store: desiredTags["store"],
Location: desiredTags["location"],
Department: desiredTags["department"],
PriceCents: 99,
PreviousPriceCents: 101,
WeightGrams: 1000,
Timestamp: time.Now(),
})

if want, got := 3, len(gMock.writtenPoints); want != got {
t.Errorf("want %d, got %d", want, got)
}

// Check the first written point.
p := gMock.writtenPoints[0]
if want, got := "product", p.Name(); want != got {
t.Errorf("want %s, got %s", want, got)
}

for _, tag := range p.TagList() {
if want, got := desiredTags[tag.Key], tag.Value; want != got {
t.Errorf("want %s, got %s", want, got)
}
}

for _, field := range p.FieldList() {
switch field.Key {
case "cents":
if want, got := int64(100), field.Value.(int64); want != got {
t.Errorf("want %v, got %v", want, got)
}
case "grams":
if want, got := int64(1000), field.Value.(int64); want != got {
t.Errorf("want %v, got %v", want, got)
}
case "cents_change":
t.Errorf("unexpected field %s", field.Key)
default:
t.Errorf("unexpected field %s", field.Key)
}
}

// Now check the second written point.
p = gMock.writtenPoints[1]

for _, field := range p.FieldList() {
switch field.Key {
case "cents":
if want, got := int64(101), field.Value.(int64); want != got {
t.Errorf("want %v, got %v", want, got)
}
case "grams":
continue
case "cents_change":
if want, got := int64(1), field.Value.(int64); want != got {
t.Errorf("want %v, got %v", want, got)
}
default:
t.Errorf("unexpected field %s", field.Key)
}
}

// Now check the third written point.
p = gMock.writtenPoints[2]

for _, field := range p.FieldList() {
switch field.Key {
case "cents":
continue
case "grams":
continue
case "cents_change":
if want, got := int64(-2), field.Value.(int64); want != got {
t.Errorf("want %v, got %v", want, got)
}
default:
t.Errorf("unexpected field %s", field.Key)
}
}

}
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ import (
woolworths "github.com/tjhowse/aus_grocery_price_database/internal/woolworths"
)

const VERSION = "0.0.37"
const VERSION = "0.0.38"
const SYSTEM_STATUS_UPDATE_INTERVAL_SECONDS = 60

type config struct {
23 changes: 22 additions & 1 deletion main_test.go
Original file line number Diff line number Diff line change
@@ -73,7 +73,7 @@ func (m *MockGroceryStore) Run(chan struct{}) {
func (m *MockGroceryStore) GetSharedProductsUpdatedAfter(cutoff time.Time, count int) ([]shared.ProductInfo, error) {
var productIDs []shared.ProductInfo

for i := 0; i < count; i++ {
for i := 0; i < count-1; i++ {

productIDs = append(productIDs, shared.ProductInfo{
ID: strconv.Itoa(i),
@@ -87,6 +87,20 @@ func (m *MockGroceryStore) GetSharedProductsUpdatedAfter(cutoff time.Time, count
Timestamp: time.Now().Add(-5 * time.Minute),
})
}
// Add a duplicate at the end with a different price to trigger the price change logging.
i := 1
productIDs = append(productIDs, shared.ProductInfo{
ID: strconv.Itoa(i),
Name: "Test Product" + strconv.Itoa(i),
Description: "Test Description" + strconv.Itoa(i),
Store: "Test Store" + strconv.Itoa(i),
Department: "Test Department" + strconv.Itoa(i),
Location: "Test Location" + strconv.Itoa(i),
PriceCents: 100 + i + 1,
PreviousPriceCents: 100 + i,
WeightGrams: 1000 + i,
Timestamp: time.Now().Add(-5 * time.Minute),
})

return productIDs, nil
}
@@ -139,6 +153,13 @@ func TestRun(t *testing.T) {
if want, got := 100, mockInfluxDB.writtenProductDataPoints[0].PriceCents; want != got {
t.Errorf("Expected %d, got %d", want, got)
}

if want, got := 102, mockInfluxDB.writtenProductDataPoints[len(mockInfluxDB.writtenProductDataPoints)-1].PriceCents; want != got {
t.Errorf("Expected %d, got %d", want, got)
}
if want, got := 101, mockInfluxDB.writtenProductDataPoints[len(mockInfluxDB.writtenProductDataPoints)-1].PreviousPriceCents; want != got {
t.Errorf("Expected %d, got %d", want, got)
}
// Give time for the timeseries database to be closed down
time.Sleep(2 * time.Second)
if !mockInfluxDB.closed {

0 comments on commit 90c54f8

Please sign in to comment.