aboutsummaryrefslogtreecommitdiffstats
path: root/lulutest
diff options
context:
space:
mode:
authorSam Anthony <sam@samanthony.xyz>2026-08-05 19:25:03 -0230
committerSam Anthony <sam@samanthony.xyz>2026-08-05 19:25:03 -0230
commita2668014405c27671adeda4921431734afa8bec1 (patch)
tree87fae4038552c90372ae7a8a0887e3ac8cef4aad /lulutest
parentabd51689cef03fa3daecb9abbb54b762e3ab02ac (diff)
downloadlulu-fd7580215e49481bd348206d52fb351d87b6e666.zip
lulutest: mock testsHEADv0.3.0mockmaster
Diffstat (limited to 'lulutest')
-rw-r--r--lulutest/lulutest.go2
-rw-r--r--lulutest/mock.go58
-rw-r--r--lulutest/mock_test.go315
3 files changed, 355 insertions, 20 deletions
diff --git a/lulutest/lulutest.go b/lulutest/lulutest.go
index 273128b..707a77d 100644
--- a/lulutest/lulutest.go
+++ b/lulutest/lulutest.go
@@ -11,7 +11,7 @@ import (
var (
DummyEmailAddr = lulu.MustParseEmailAddress("test@example.com")
DummyPkgId = lulu.PkgId{lulu.UsTrade, lulu.Mono, lulu.Standard, lulu.Perfect, lulu.P60UncoatedWhite, lulu.Matte, lulu.NoLinen, lulu.NoFoil}
- DummyShipAddr = lulu.ShippingAddress{
+ DummyShipAddr = lulu.ShippingAddress{
City: "Lübeck",
CountryCode: "DE",
Name: "Hans Dampf",
diff --git a/lulutest/mock.go b/lulutest/mock.go
index 15d3e36..95406d6 100644
--- a/lulutest/mock.go
+++ b/lulutest/mock.go
@@ -13,7 +13,7 @@ import (
"git.samanthony.xyz/lulu"
)
-type mockClient struct {
+type MockClient struct {
mu sync.Mutex
ints []lulu.InteriorValidation
covs []lulu.CoverValidation
@@ -21,13 +21,13 @@ type mockClient struct {
printables map[lulu.PrintableId]lulu.Printable
}
-func NewMockClient() lulu.Client {
- return &mockClient{
+func NewMockClient() *MockClient {
+ return &MockClient{
printables: make(map[lulu.PrintableId]lulu.Printable),
}
}
-func (c *mockClient) StartInteriorValidation(srcUrl string, mfg lulu.PkgId) (uint, error) {
+func (c *MockClient) StartInteriorValidation(srcUrl string, mfg lulu.PkgId) (uint, error) {
c.mu.Lock()
defer c.mu.Unlock()
id := uint(len(c.ints))
@@ -36,11 +36,11 @@ func (c *mockClient) StartInteriorValidation(srcUrl string, mfg lulu.PkgId) (uin
return id, nil
}
-func (c *mockClient) StartInteriorValidationBasic(srcUrl string) (uint, error) {
+func (c *MockClient) StartInteriorValidationBasic(srcUrl string) (uint, error) {
return c.StartInteriorValidation(srcUrl, lulu.PkgId{})
}
-func (c *mockClient) GetInteriorValidation(id uint) (lulu.InteriorValidation, error) {
+func (c *MockClient) GetInteriorValidation(id uint) (lulu.InteriorValidation, error) {
c.mu.Lock()
defer c.mu.Unlock()
if id >= uint(len(c.ints)) {
@@ -49,11 +49,11 @@ func (c *mockClient) GetInteriorValidation(id uint) (lulu.InteriorValidation, er
return c.ints[id], nil
}
-func (c *mockClient) CoverDimensions(mfg lulu.PkgId, npages uint, unit lulu.Unit) (lulu.CoverDimensions, error) {
+func (c *MockClient) CoverDimensions(mfg lulu.PkgId, npages uint, unit lulu.Unit) (lulu.CoverDimensions, error) {
return lulu.CoverDimensions{123.4, 56.78, unit}, nil
}
-func (c *mockClient) StartCoverValidation(srcUrl string, mfg lulu.PkgId, npages uint) (uint, error) {
+func (c *MockClient) StartCoverValidation(srcUrl string, mfg lulu.PkgId, npages uint) (uint, error) {
c.mu.Lock()
defer c.mu.Unlock()
id := uint(len(c.covs))
@@ -62,7 +62,7 @@ func (c *mockClient) StartCoverValidation(srcUrl string, mfg lulu.PkgId, npages
return id, nil
}
-func (c *mockClient) GetCoverValidation(id uint) (lulu.CoverValidation, error) {
+func (c *MockClient) GetCoverValidation(id uint) (lulu.CoverValidation, error) {
c.mu.Lock()
defer c.mu.Unlock()
if id >= uint(len(c.covs)) {
@@ -71,7 +71,7 @@ func (c *mockClient) GetCoverValidation(id uint) (lulu.CoverValidation, error) {
return c.covs[id], nil
}
-func (c *mockClient) Cost(items []lulu.CostLineItem, addr lulu.ShippingAddress, opt lulu.ShippingLevel) (lulu.Cost, lulu.ShippingAddressValidation, error) {
+func (c *MockClient) Cost(items []lulu.CostLineItem, addr lulu.ShippingAddress, opt lulu.ShippingLevel) (lulu.Cost, lulu.ShippingAddressValidation, error) {
var cost lulu.Cost
var subtotal decimal.Decimal
var tax decimal.Decimal
@@ -122,12 +122,21 @@ func (c *mockClient) Cost(items []lulu.CostLineItem, addr lulu.ShippingAddress,
type job struct {
j <-chan lulu.PrintJob
cancel chan chan bool
+ quit chan struct{}
}
+const (
+ shipDelay = time.Hour
+ deliverDelay = 56 * time.Hour
+)
+
func print(id uint64, eid string, contact lulu.EmailAddress, delay time.Duration, addr lulu.ShippingAddress, opt lulu.ShippingLevel, addrVal lulu.ShippingAddressValidation, items []lulu.LineItem, cost lulu.Cost) job {
jc := make(chan lulu.PrintJob)
cancel := make(chan chan bool)
+ quit := make(chan struct{})
go func() {
+ defer close(jc)
+ defer close(cancel)
j := lulu.PrintJob{
Contact: contact,
Cost: cost,
@@ -158,8 +167,8 @@ func print(id uint64, eid string, contact lulu.EmailAddress, delay time.Duration
}
setStatus(lulu.OrderProductionDelayed)
prod := time.After(delay)
- ship := time.After(delay + time.Hour)
- arrive := time.After(delay + time.Hour + 56*time.Hour)
+ ship := time.After(delay + shipDelay)
+ arrive := time.After(delay + shipDelay + deliverDelay)
for {
select {
case jc <- j:
@@ -182,13 +191,24 @@ func print(id uint64, eid string, contact lulu.EmailAddress, delay time.Duration
if j.Status.Status == lulu.OrderShipped {
setStatus(lulu.OrderDelivered)
}
+ case <-quit:
+ return
}
}
}()
- return job{jc, cancel}
+ return job{jc, cancel, quit}
+}
+
+// Close terminates the client's goroutines.
+func (c *MockClient) Close() {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ for _, j := range c.jobs {
+ close(j.quit)
+ }
}
-func (c *mockClient) Print(contact lulu.EmailAddress, eid string, delay time.Duration, addr lulu.ShippingAddress, opt lulu.ShippingLevel, items []lulu.Printable) (lulu.PrintJob, error) {
+func (c *MockClient) Print(contact lulu.EmailAddress, eid string, delay time.Duration, addr lulu.ShippingAddress, opt lulu.ShippingLevel, items []lulu.Printable) (lulu.PrintJob, error) {
c.mu.Lock()
defer c.mu.Unlock()
@@ -213,7 +233,7 @@ func (c *mockClient) Print(contact lulu.EmailAddress, eid string, delay time.Dur
return c.print(id, eid, contact, delay, addr, opt, lineItems, costItems)
}
-func (c *mockClient) Reprint(contact lulu.EmailAddress, eid string, delay time.Duration, addr lulu.ShippingAddress, opt lulu.ShippingLevel, items []lulu.Reprintable) (lulu.PrintJob, error) {
+func (c *MockClient) Reprint(contact lulu.EmailAddress, eid string, delay time.Duration, addr lulu.ShippingAddress, opt lulu.ShippingLevel, items []lulu.Reprintable) (lulu.PrintJob, error) {
c.mu.Lock()
defer c.mu.Unlock()
@@ -241,7 +261,7 @@ func (c *mockClient) Reprint(contact lulu.EmailAddress, eid string, delay time.D
return c.print(id, eid, contact, delay, addr, opt, lineItems, costItems)
}
-func (c *mockClient) print(id uint64, eid string, contact lulu.EmailAddress, delay time.Duration, addr lulu.ShippingAddress, opt lulu.ShippingLevel, lineItems []lulu.LineItem, costItems []lulu.CostLineItem) (lulu.PrintJob, error) {
+func (c *MockClient) print(id uint64, eid string, contact lulu.EmailAddress, delay time.Duration, addr lulu.ShippingAddress, opt lulu.ShippingLevel, lineItems []lulu.LineItem, costItems []lulu.CostLineItem) (lulu.PrintJob, error) {
cost, addrVal, err := c.Cost(costItems, addr, opt)
if err != nil {
return lulu.PrintJob{}, err
@@ -251,7 +271,7 @@ func (c *mockClient) print(id uint64, eid string, contact lulu.EmailAddress, del
return <-j.j, nil
}
-func (c *mockClient) Job(id uint64) (lulu.PrintJob, error) {
+func (c *MockClient) Job(id uint64) (lulu.PrintJob, error) {
c.mu.Lock()
defer c.mu.Unlock()
if id < uint64(len(c.jobs)) {
@@ -261,7 +281,7 @@ func (c *mockClient) Job(id uint64) (lulu.PrintJob, error) {
}
// Ignores queries
-func (c *mockClient) Jobs(qs ...lulu.PrintJobQuery) ([]lulu.PrintJob, error) {
+func (c *MockClient) Jobs(qs ...lulu.PrintJobQuery) ([]lulu.PrintJob, error) {
c.mu.Lock()
defer c.mu.Unlock()
js := make([]lulu.PrintJob, len(c.jobs))
@@ -271,7 +291,7 @@ func (c *mockClient) Jobs(qs ...lulu.PrintJobQuery) ([]lulu.PrintJob, error) {
return js, nil
}
-func (c *mockClient) Cancel(id uint64) error {
+func (c *MockClient) Cancel(id uint64) error {
c.mu.Lock()
defer c.mu.Unlock()
if id < uint64(len(c.jobs)) {
diff --git a/lulutest/mock_test.go b/lulutest/mock_test.go
new file mode 100644
index 0000000..c1d4b89
--- /dev/null
+++ b/lulutest/mock_test.go
@@ -0,0 +1,315 @@
+package lulutest
+
+import (
+ "fmt"
+ "testing"
+ "testing/synctest"
+ "time"
+
+ "github.com/shopspring/decimal"
+ "github.com/stretchr/testify/require"
+
+ "git.samanthony.xyz/lulu"
+)
+
+const (
+ interiorUrl = "https://www.dropbox.com/sh/p3zh22vzsaegiri/AACOUn3LFKsITDzylh13bQpsa/161025/thesis2.pdf?dl=1"
+ coverUrl = "https://www.dropbox.com/sh/p3zh22vzsaegiri/AADP367j0bTWlt8fCu-_tm2ia/161025/139056_cover.pdf?dl=1"
+)
+
+func TestStartAndGetInteriorValidation(t *testing.T) {
+ c := NewMockClient()
+ defer c.Close()
+ id, err := c.StartInteriorValidation(interiorUrl, DummyPkgId)
+ require.NoError(t, err)
+
+ val, err := c.GetInteriorValidation(id)
+ require.NoError(t, err)
+ require.Equal(t, id, val.Id)
+ require.Equal(t, interiorUrl, val.SrcUrl)
+ require.Equal(t, lulu.InteriorStatusNormalized, val.Status)
+}
+
+func TestStartInteriorValidationBasic(t *testing.T) {
+ c := NewMockClient()
+ defer c.Close()
+ id, err := c.StartInteriorValidationBasic(interiorUrl)
+ require.NoError(t, err)
+
+ val, err := c.GetInteriorValidation(id)
+ require.NoError(t, err)
+ require.Equal(t, id, val.Id)
+ require.Equal(t, interiorUrl, val.SrcUrl)
+}
+
+func TestGetInteriorValidationNotFound(t *testing.T) {
+ c := NewMockClient()
+ defer c.Close()
+ _, err := c.GetInteriorValidation(0)
+ require.Error(t, err)
+}
+
+func TestCoverDimensions(t *testing.T) {
+ c := NewMockClient()
+ defer c.Close()
+ dims, err := c.CoverDimensions(DummyPkgId, 210, lulu.Points)
+ require.NoError(t, err)
+ require.Equal(t, lulu.Points, dims.Unit)
+ require.NotZero(t, dims.Width)
+ require.NotZero(t, dims.Height)
+}
+
+func TestStartAndGetCoverValidation(t *testing.T) {
+ c := NewMockClient()
+ defer c.Close()
+ id, err := c.StartCoverValidation(coverUrl, DummyPkgId, 210)
+ require.NoError(t, err)
+
+ val, err := c.GetCoverValidation(id)
+ require.NoError(t, err)
+ require.Equal(t, id, val.Id)
+ require.Equal(t, coverUrl, val.SrcUrl)
+ require.Equal(t, uint(210), val.NPages)
+ require.Equal(t, lulu.CoverStatusNormalized, val.Status)
+}
+
+func TestGetCoverValidationNotFound(t *testing.T) {
+ c := NewMockClient()
+ defer c.Close()
+ _, err := c.GetCoverValidation(0)
+ require.Error(t, err)
+}
+
+func TestCost(t *testing.T) {
+ c := NewMockClient()
+ defer c.Close()
+ items := []lulu.CostLineItem{
+ {Mfg: DummyPkgId, Quantity: 2},
+ {Mfg: DummyPkgId, Quantity: 1},
+ }
+ addr := DummyShipAddr
+ addr.Street1 = "1 Main St"
+
+ cost, val, err := c.Cost(items, addr, lulu.Mail)
+ require.NoError(t, err)
+
+ require.Equal(t, "USD", cost.Currency)
+ require.Len(t, cost.LineItemCosts, len(items))
+ for i, it := range cost.LineItemCosts {
+ require.Equal(t, items[i].Quantity, it.Quantity)
+ require.True(t, it.TotalCostInclTax.GreaterThan(decimal.Zero))
+ }
+ require.True(t, cost.TotalCostInclTax.GreaterThan(cost.TotalCostExclTax))
+
+ require.Equal(t, addr, val.Address)
+ require.Equal(t, "1 Main rd", val.Suggested.Street1)
+}
+
+func TestPrint(t *testing.T) {
+ c := NewMockClient()
+ defer c.Close()
+ items := MakePrintables(c, 2)
+
+ j, err := c.Print(DummyEmailAddr, "ext-1", 5*time.Minute, DummyShipAddr, lulu.Mail, items)
+ require.NoError(t, err)
+
+ require.Zero(t, j.Id)
+ require.Equal(t, "ext-1", j.ExternalId)
+ require.Equal(t, DummyEmailAddr, j.Contact)
+ require.Equal(t, lulu.OrderProductionDelayed, j.Status.Status)
+ require.Len(t, j.Items, len(items))
+
+ seen := make(map[lulu.PrintableId]bool)
+ for i, it := range j.Items {
+ require.Equal(t, items[i].ExternalId, it.ExternalId)
+ require.Equal(t, items[i].Mfg, it.Mfg)
+ require.Equal(t, items[i].Quantity, it.Quantity)
+ require.NotEmpty(t, it.PrintableId)
+ require.False(t, seen[it.PrintableId], "duplicate printable id %q", it.PrintableId)
+ seen[it.PrintableId] = true
+ }
+
+ // A second job should get the next sequential id.
+ j2, err := c.Print(DummyEmailAddr, "ext-2", 5*time.Minute, DummyShipAddr, lulu.Mail, items)
+ require.NoError(t, err)
+ require.Equal(t, j.Id+1, j2.Id)
+}
+
+func TestReprint(t *testing.T) {
+ c := NewMockClient()
+ defer c.Close()
+ items := MakePrintables(c, 2)
+
+ j, err := c.Print(DummyEmailAddr, "orig", 5*time.Minute, DummyShipAddr, lulu.Mail, items)
+ require.NoError(t, err)
+
+ rpbs := make([]lulu.Reprintable, len(j.Items))
+ for i, it := range j.Items {
+ rpbs[i] = lulu.Reprintable{
+ ExternalId: it.ExternalId,
+ PrintableId: it.PrintableId,
+ Quantity: it.Quantity,
+ Title: it.Title,
+ }
+ }
+
+ rj, err := c.Reprint(DummyEmailAddr, "reprint", 5*time.Minute, DummyShipAddr, lulu.Mail, rpbs)
+ require.NoError(t, err)
+ require.Equal(t, j.Id+1, rj.Id)
+ require.Len(t, rj.Items, len(rpbs))
+ for i, it := range rj.Items {
+ require.Equal(t, rpbs[i].PrintableId, it.PrintableId)
+ require.Equal(t, items[i].Mfg, it.Mfg)
+ }
+}
+
+func TestMakeReprintables(t *testing.T) {
+ c := NewMockClient()
+ defer c.Close()
+ rpbs, err := MakeReprintables(c, 2)
+ require.NoError(t, err)
+ require.Len(t, rpbs, 2)
+ for _, r := range rpbs {
+ require.NotEmpty(t, r.PrintableId)
+ require.NotZero(t, r.Quantity)
+ }
+
+ j, err := c.Reprint(DummyEmailAddr, "eid", 5*time.Minute, DummyShipAddr, lulu.Mail, rpbs)
+ require.NoError(t, err)
+ require.Len(t, j.Items, 2)
+}
+
+func TestReprintUnknownPrintable(t *testing.T) {
+ c := NewMockClient()
+ defer c.Close()
+ rpbs := []lulu.Reprintable{{
+ ExternalId: "ext",
+ PrintableId: lulu.PrintableId("does-not-exist"),
+ Quantity: 1,
+ Title: "Ghost",
+ }}
+ _, err := c.Reprint(DummyEmailAddr, "eid", 5*time.Minute, DummyShipAddr, lulu.Mail, rpbs)
+ require.Error(t, err)
+}
+
+func TestJob(t *testing.T) {
+ c := NewMockClient()
+ defer c.Close()
+ items := MakePrintables(c, 1)
+ j, err := c.Print(DummyEmailAddr, "eid", 5*time.Minute, DummyShipAddr, lulu.Mail, items)
+ require.NoError(t, err)
+
+ got, err := c.Job(j.Id)
+ require.NoError(t, err)
+ require.Equal(t, j.Id, got.Id)
+ require.Equal(t, j.ExternalId, got.ExternalId)
+}
+
+func TestJobNotFound(t *testing.T) {
+ c := NewMockClient()
+ defer c.Close()
+ _, err := c.Job(0)
+ require.Error(t, err)
+}
+
+func TestJobs(t *testing.T) {
+ c := NewMockClient()
+ defer c.Close()
+ items := MakePrintables(c, 1)
+
+ want := make(map[uint64]string)
+ for i := range 3 {
+ eid := fmt.Sprintf("eid-%d", i)
+ j, err := c.Print(DummyEmailAddr, eid, 5*time.Minute, DummyShipAddr, lulu.Mail, items)
+ require.NoError(t, err)
+ want[j.Id] = eid
+ }
+
+ js, err := c.Jobs()
+ require.NoError(t, err)
+ require.Len(t, js, len(want))
+ for _, j := range js {
+ require.Equal(t, want[j.Id], j.ExternalId)
+ }
+}
+
+func TestJobsEmpty(t *testing.T) {
+ c := NewMockClient()
+ defer c.Close()
+ js, err := c.Jobs()
+ require.NoError(t, err)
+ require.Empty(t, js)
+}
+
+func TestCancelWhileDelayed(t *testing.T) {
+ c := NewMockClient()
+ defer c.Close()
+ items := MakePrintables(c, 1)
+ j, err := c.Print(DummyEmailAddr, "eid", 5*time.Minute, DummyShipAddr, lulu.Mail, items)
+ require.NoError(t, err)
+
+ require.NoError(t, c.Cancel(j.Id))
+
+ got, err := c.Job(j.Id)
+ require.NoError(t, err)
+ require.Equal(t, lulu.OrderCanceled, got.Status.Status)
+}
+
+func TestCancelAfterProduction(t *testing.T) {
+ synctest.Test(t, func(t *testing.T) {
+ c := NewMockClient()
+ defer c.Close()
+
+ items := MakePrintables(c, 1)
+ delay := 10 * time.Minute
+ j, err := c.Print(DummyEmailAddr, "eid", delay, DummyShipAddr, lulu.Mail, items)
+ require.NoError(t, err)
+
+ time.Sleep(delay + time.Millisecond) // let the prod timer fire
+
+ got, err := c.Job(j.Id)
+ require.NoError(t, err)
+ require.Equal(t, lulu.OrderInProduction, got.Status.Status)
+
+ err = c.Cancel(j.Id)
+ require.Error(t, err)
+
+ got, err = c.Job(j.Id)
+ require.NoError(t, err)
+ require.Equal(t, lulu.OrderInProduction, got.Status.Status)
+ })
+}
+
+func TestCancelNotFound(t *testing.T) {
+ c := NewMockClient()
+ defer c.Close()
+ require.Error(t, c.Cancel(0))
+}
+
+func TestJobLifecycle(t *testing.T) {
+ synctest.Test(t, func(t *testing.T) {
+ c := NewMockClient()
+ defer c.Close()
+
+ items := MakePrintables(c, 1)
+ delay := 10 * time.Minute
+ j, err := c.Print(DummyEmailAddr, "eid", delay, DummyShipAddr, lulu.Mail, items)
+ require.NoError(t, err)
+ require.Equal(t, lulu.OrderProductionDelayed, j.Status.Status)
+
+ time.Sleep(delay + time.Millisecond) // let the prod timer fire
+ got, err := c.Job(j.Id)
+ require.NoError(t, err)
+ require.Equal(t, lulu.OrderInProduction, got.Status.Status)
+
+ time.Sleep(shipDelay) // let the ship timer fire
+ got, err = c.Job(j.Id)
+ require.NoError(t, err)
+ require.Equal(t, lulu.OrderShipped, got.Status.Status)
+
+ time.Sleep(deliverDelay) // let the arrive timer fire
+ got, err = c.Job(j.Id)
+ require.NoError(t, err)
+ require.Equal(t, lulu.OrderDelivered, got.Status.Status)
+ })
+}