-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial commit with examples and tests
Signed-off-by: Nikki Attea <[email protected]>
- Loading branch information
Nikki Attea
committed
Feb 1, 2020
0 parents
commit 651a0f6
Showing
4 changed files
with
380 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
module github.com/nikkixdev/beer-testing | ||
|
||
go 1.13 | ||
|
||
require ( | ||
github.com/kr/pretty v0.1.0 // indirect | ||
github.com/sirupsen/logrus v1.4.2 | ||
github.com/stretchr/testify v1.4.0 | ||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= | ||
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/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= | ||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= | ||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= | ||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= | ||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | ||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= | ||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | ||
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/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= | ||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= | ||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= | ||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= | ||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | ||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= | ||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= | ||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= | ||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= | ||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= | ||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
package main | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"io/ioutil" | ||
"net/http" | ||
"sync" | ||
"time" | ||
|
||
"github.com/sirupsen/logrus" | ||
) | ||
|
||
// Cart represents a shopping cart. | ||
type Cart struct { | ||
Cases []*Case | ||
} | ||
|
||
// Case represents a case of beer. | ||
type Case struct { | ||
Count int | ||
Beer *Beer | ||
Price float64 | ||
} | ||
|
||
// Beer represents a type of beer. | ||
type Beer struct { | ||
Brand string | ||
Name string | ||
Ounces float64 | ||
} | ||
|
||
// NewCart initializes a new shopping cart. | ||
func NewCart() *Cart { | ||
return &Cart{} | ||
} | ||
|
||
// AddCase adds a case of beer to the shopping cart. | ||
func (c *Cart) AddCase(beerCase *Case) { | ||
c.Cases = append(c.Cases, beerCase) | ||
} | ||
|
||
// Subtotal calculates the subtotal of the shopping cart. | ||
func (c *Cart) Subtotal() float64 { | ||
var subtotal float64 | ||
for _, beerCase := range c.Cases { | ||
subtotal += beerCase.Price | ||
} | ||
return subtotal | ||
} | ||
|
||
// ProcessPayment sends the total to an external payment api. | ||
func ProcessPayment(paymentServer string, total float64) ([]byte, error) { | ||
b, _ := json.Marshal(total) | ||
resp, err := http.Post(paymentServer, "application/json", bytes.NewBuffer(b)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer resp.Body.Close() | ||
if resp.StatusCode >= 400 { | ||
return nil, fmt.Errorf("payment server error: %d", resp.StatusCode) | ||
} | ||
return ioutil.ReadAll(resp.Body) | ||
} | ||
|
||
// PlaceOrder places the order in the warehouse. | ||
func (o *OrderHandler) PlaceOrder(ctx context.Context, cart *Cart) error { | ||
o.ProcessedOrders = append(o.ProcessedOrders, cart) | ||
return nil | ||
} | ||
|
||
// OrderHandler represents a concurrent order handler. | ||
type OrderHandler struct { | ||
ProcessedOrders []*Cart | ||
messageChan chan interface{} | ||
} | ||
|
||
var logger = logrus.WithFields(logrus.Fields{ | ||
"component": "beer", | ||
}) | ||
|
||
// startOrderHandler listens to the message channel and handles incoming orders. | ||
func (o *OrderHandler) startOrderHandler(ctx context.Context) { | ||
for { | ||
msg, ok := <-o.messageChan | ||
if !ok { | ||
logger.Debug("message channel closed") | ||
return | ||
} | ||
|
||
cart, ok := msg.(*Cart) | ||
if ok { | ||
if err := o.PlaceOrder(ctx, cart); err != nil { | ||
logger.WithError(err).Error("error placing order") | ||
continue | ||
} | ||
logger.Info("successfully placed order") | ||
continue | ||
} | ||
|
||
logger.WithField("msg", msg).Errorf("received invalid message on message channel") | ||
} | ||
} | ||
|
||
// Subscription represents a shopping cart. | ||
type Subscription struct { | ||
cart *Cart | ||
interval time.Duration | ||
messageChan chan interface{} | ||
mu sync.Mutex | ||
} | ||
|
||
// GetCart safely retrieves the subscriptions shopping cart. | ||
func (s *Subscription) GetCart() *Cart { | ||
s.mu.Lock() | ||
defer s.mu.Unlock() | ||
return s.cart | ||
} | ||
|
||
// SetCart safely sets the subscriptions shopping cart. | ||
func (s *Subscription) SetCart(c *Cart) { | ||
s.mu.Lock() | ||
defer s.mu.Unlock() | ||
s.cart = c | ||
} | ||
|
||
// GetInterval safely retrieves the subscriptions interval. | ||
func (s *Subscription) GetInterval() time.Duration { | ||
s.mu.Lock() | ||
defer s.mu.Unlock() | ||
return s.interval | ||
} | ||
|
||
// SetInterval safely sets the subscriptions interval. | ||
func (s *Subscription) SetInterval(t time.Duration) { | ||
s.mu.Lock() | ||
defer s.mu.Unlock() | ||
s.interval = t | ||
} | ||
|
||
// startSubscriptionTimer starts a timer and fires the cart to the | ||
// order handler when the order is ready. | ||
func (s *Subscription) startSubscriptionTimer(ctx context.Context) { | ||
ticker := time.NewTicker(s.GetInterval()) | ||
defer ticker.Stop() | ||
for { | ||
select { | ||
case <-ctx.Done(): | ||
return | ||
case <-ticker.C: | ||
s.messageChan <- s.GetCart() | ||
} | ||
} | ||
} | ||
|
||
// FixtureBeer creates a Beer fixture for use in test. | ||
func FixtureBeer(brand string, name string, ounces float64) *Beer { | ||
return &Beer{ | ||
Brand: brand, | ||
Name: name, | ||
Ounces: ounces, | ||
} | ||
} | ||
|
||
// FixtureCase creates a Case fixture for use in test. | ||
func FixtureCase(count int, beer *Beer, price float64) *Case { | ||
return &Case{ | ||
Count: count, | ||
Beer: beer, | ||
Price: price, | ||
} | ||
} | ||
|
||
// FixtureCart creates a Cart fixture for use in test. | ||
func FixtureCart() *Cart { | ||
return &Cart{ | ||
Cases: []*Case{FixtureCase(4, FixtureBeer("Duvel", "Tripel Hop", 11.0), 14)}, | ||
} | ||
} | ||
|
||
func main() {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestAddCaseTesting(t *testing.T) { | ||
cart := NewCart() | ||
if len(cart.Cases) != 0 { | ||
t.Fatal("expected empty cart") | ||
} | ||
|
||
blueLight := FixtureBeer("Labatt", "Blue Light", 12.0) | ||
cart.AddCase(FixtureCase(6, blueLight, 10.99)) | ||
if len(cart.Cases) != 1 { | ||
t.Fatal("expected 1 case in cart") | ||
} | ||
} | ||
|
||
func TestAddCaseAssert(t *testing.T) { | ||
cart := NewCart() | ||
assert.Equal(t, 0, len(cart.Cases)) | ||
|
||
blueLight := FixtureBeer("Labatt", "Blue Light", 12.0) | ||
cart.AddCase(FixtureCase(6, blueLight, 10.99)) | ||
assert.Equal(t, 1, len(cart.Cases)) | ||
} | ||
|
||
func TestSubtotal(t *testing.T) { | ||
cart := NewCart() | ||
assert.Equal(t, 0, len(cart.Cases)) | ||
|
||
duvelHop := FixtureBeer("Duvel", "Tripel Hop", 11.0) | ||
cart.AddCase(FixtureCase(4, duvelHop, 14.99)) | ||
blueLight := FixtureBeer("Labatt", "Blue Light", 12.0) | ||
cart.AddCase(FixtureCase(30, blueLight, 24.99)) | ||
assert.Equal(t, 39.98, cart.Subtotal()) | ||
} | ||
|
||
func TestSubtotalSuite(t *testing.T) { | ||
testCases := []struct { | ||
name string | ||
cart *Cart | ||
subtotal float64 | ||
}{ | ||
{ | ||
name: "Empty cart", | ||
cart: &Cart{}, | ||
subtotal: 0, | ||
}, | ||
{ | ||
name: "Party time", | ||
cart: &Cart{Cases: []*Case{ | ||
FixtureCase(4, FixtureBeer("Duvel", "Tripel Hop", 11.0), 14.99), | ||
FixtureCase(30, FixtureBeer("Labatt", "Blue Light", 12.0), 24.99), | ||
FixtureCase(30, FixtureBeer("Labatt", "Blue Light", 12.0), 24.99), | ||
}}, | ||
subtotal: 64.97, | ||
}, | ||
{ | ||
name: "Negative", | ||
cart: &Cart{Cases: []*Case{ | ||
FixtureCase(4, FixtureBeer("Duvel", "Tripel Hop", 11.0), -14), | ||
FixtureCase(30, FixtureBeer("Labatt", "Blue Light", 12.0), 24), | ||
}}, | ||
subtotal: 10.00, | ||
}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
assert.Equal(t, tc.subtotal, tc.cart.Subtotal()) | ||
}) | ||
} | ||
} | ||
|
||
func TestProcessPayment(t *testing.T) { | ||
testCases := []struct { | ||
name string | ||
handler http.HandlerFunc | ||
expectedError error | ||
expectedBody []byte | ||
}{ | ||
{ | ||
name: "OK", | ||
handler: func(w http.ResponseWriter, r *http.Request) { | ||
w.Write([]byte(`OK`)) | ||
w.WriteHeader(http.StatusOK) | ||
}, | ||
expectedError: nil, | ||
expectedBody: []byte(`OK`), | ||
}, | ||
{ | ||
name: "Internal server error", | ||
handler: func(w http.ResponseWriter, r *http.Request) { | ||
w.WriteHeader(http.StatusInternalServerError) | ||
}, | ||
expectedError: fmt.Errorf("payment server error: %d", http.StatusInternalServerError), | ||
}, | ||
} | ||
|
||
for _, tc := range testCases { | ||
t.Run(tc.name, func(t *testing.T) { | ||
ts := httptest.NewServer(http.HandlerFunc(tc.handler)) | ||
defer ts.Close() | ||
body, err := ProcessPayment(ts.URL, 21.11) | ||
assert.Equal(t, tc.expectedError, err) | ||
assert.Equal(t, tc.expectedBody, body) | ||
}) | ||
} | ||
} | ||
|
||
func TestStartSubscriptionTimer(t *testing.T) { | ||
ctx := context.Background() | ||
cart1 := &Cart{Cases: []*Case{FixtureCase(4, FixtureBeer("Duvel", "Tripel Hop", 11.0), 14)}} | ||
cart2 := &Cart{Cases: []*Case{FixtureCase(30, FixtureBeer("Labatt", "Blue Light", 12.0), 24)}} | ||
subscription := &Subscription{ | ||
cart: cart1, | ||
interval: time.Duration(1) * time.Second, | ||
messageChan: make(chan interface{}), | ||
} | ||
|
||
go subscription.startSubscriptionTimer(ctx) | ||
msg := <-subscription.messageChan | ||
order, ok := msg.(*Cart) | ||
if !ok { | ||
t.Fatal("received invalid message on message channel") | ||
} | ||
assert.Equal(t, cart1, order) | ||
|
||
subscription.SetCart(cart2) | ||
msg = <-subscription.messageChan | ||
order, ok = msg.(*Cart) | ||
if !ok { | ||
t.Fatal("received invalid message on message channel") | ||
} | ||
assert.Equal(t, cart2, order) | ||
} | ||
|
||
func TestStartOrderHandler(t *testing.T) { | ||
handler := &OrderHandler{ | ||
messageChan: make(chan interface{}), | ||
} | ||
go handler.startOrderHandler(context.Background()) | ||
assert.Equal(t, 0, len(handler.ProcessedOrders)) | ||
|
||
handler.messageChan <- FixtureCart() | ||
handler.messageChan <- FixtureCart() | ||
handler.messageChan <- FixtureCase(30, FixtureBeer("Labatt", "Blue Light", 12.0), 24) | ||
assert.Equal(t, 2, len(handler.ProcessedOrders)) | ||
} |