aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lulutest/mock.go289
1 files changed, 289 insertions, 0 deletions
diff --git a/lulutest/mock.go b/lulutest/mock.go
new file mode 100644
index 0000000..15d3e36
--- /dev/null
+++ b/lulutest/mock.go
@@ -0,0 +1,289 @@
+package lulutest
+
+import (
+ crand "crypto/rand"
+ "fmt"
+ mrand "math/rand/v2"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/shopspring/decimal"
+
+ "git.samanthony.xyz/lulu"
+)
+
+type mockClient struct {
+ mu sync.Mutex
+ ints []lulu.InteriorValidation
+ covs []lulu.CoverValidation
+ jobs []job
+ printables map[lulu.PrintableId]lulu.Printable
+}
+
+func NewMockClient() lulu.Client {
+ return &mockClient{
+ printables: make(map[lulu.PrintableId]lulu.Printable),
+ }
+}
+
+func (c *mockClient) StartInteriorValidation(srcUrl string, mfg lulu.PkgId) (uint, error) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ id := uint(len(c.ints))
+ val := lulu.InteriorValidation{Id: id, SrcUrl: srcUrl, NPages: 123, Status: lulu.InteriorStatusNormalized}
+ c.ints = append(c.ints, val)
+ return id, nil
+}
+
+func (c *mockClient) StartInteriorValidationBasic(srcUrl string) (uint, error) {
+ return c.StartInteriorValidation(srcUrl, lulu.PkgId{})
+}
+
+func (c *mockClient) GetInteriorValidation(id uint) (lulu.InteriorValidation, error) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ if id >= uint(len(c.ints)) {
+ return lulu.InteriorValidation{}, fmt.Errorf("no interior validation job with id %d", id)
+ }
+ return c.ints[id], nil
+}
+
+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) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ id := uint(len(c.covs))
+ val := lulu.CoverValidation{Id: id, SrcUrl: srcUrl, NPages: npages, Status: lulu.CoverStatusNormalized}
+ c.covs = append(c.covs, val)
+ return id, nil
+}
+
+func (c *mockClient) GetCoverValidation(id uint) (lulu.CoverValidation, error) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ if id >= uint(len(c.covs)) {
+ return lulu.CoverValidation{}, fmt.Errorf("no cover validation job with id %d", id)
+ }
+ return c.covs[id], nil
+}
+
+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
+ taxRate := decimal.NewFromFloat(0.15)
+
+ cost.LineItemCosts = make([]lulu.LineItemCost, len(items))
+ for i, item := range items {
+ itemCost := decimal.NewFromInt(5 + mrand.Int64N(100))
+ itemTax := itemCost.Mul(taxRate).RoundCash(5)
+ itemTotal := itemCost.Add(itemTax)
+ cost.LineItemCosts[i] = lulu.LineItemCost{
+ CostExclDiscounts: itemCost,
+ TotalTax: itemTax,
+ TaxRate: taxRate,
+ Quantity: item.Quantity,
+ TotalCostExclTax: itemCost,
+ TotalCostExclDiscounts: itemTotal,
+ TotalCostInclTax: itemTotal,
+ }
+ subtotal = subtotal.Add(itemCost)
+ tax = tax.Add(itemTax)
+ }
+
+ shipCost := decimal.NewFromInt(2 + mrand.Int64N(50))
+ cost.ShipCost = lulu.FulfillmentCost{
+ TotalCostExclTax: shipCost,
+ TotalCostInclTax: shipCost,
+ }
+ subtotal = subtotal.Add(shipCost)
+
+ cost.TotalTax = tax
+ cost.TotalCostExclTax = subtotal
+ cost.TotalCostInclTax = subtotal.Add(tax)
+ cost.Currency = "USD"
+
+ sugAddr := addr
+ repl := strings.NewReplacer(
+ "st", "Rd",
+ "rd", "St",
+ "St", "rd",
+ "Rd", "st",
+ )
+ sugAddr.Street1 = repl.Replace(addr.Street1)
+
+ return cost, lulu.ShippingAddressValidation{addr, sugAddr, nil}, nil
+}
+
+type job struct {
+ j <-chan lulu.PrintJob
+ cancel chan chan bool
+}
+
+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)
+ go func() {
+ j := lulu.PrintJob{
+ Contact: contact,
+ Cost: cost,
+ Created: time.Now(),
+ Modified: time.Now(),
+ EstimatedShippingDates: lulu.EstimatedShippingDates{
+ DispatchMin: lulu.Date(time.Now().Add(delay + 5*time.Minute)),
+ DispatchMax: lulu.Date(time.Now().Add(delay + 24*time.Hour)),
+ ArrivalMin: lulu.Date(time.Now().Add(delay + 48*time.Hour)),
+ ArrivalMax: lulu.Date(time.Now().Add(delay + 72*time.Hour)),
+ },
+ ExternalId: eid,
+ Id: id,
+ Items: items,
+ OrderId: fmt.Sprintf("o%d", id),
+ ProductionDelay: delay,
+ ProductionDue: time.Now().Add(delay),
+ AddressValidation: addrVal,
+ ShipOpt: opt,
+ }
+ setStatus := func(s lulu.OrderStatus) {
+ now := time.Now()
+ j.Modified = now
+ j.Status = lulu.PrintJobStatus{
+ Changed: now,
+ Status: s,
+ }
+ }
+ setStatus(lulu.OrderProductionDelayed)
+ prod := time.After(delay)
+ ship := time.After(delay + time.Hour)
+ arrive := time.After(delay + time.Hour + 56*time.Hour)
+ for {
+ select {
+ case jc <- j:
+ case c := <-cancel:
+ if j.Status.Status == lulu.OrderProductionDelayed {
+ setStatus(lulu.OrderCanceled)
+ c <- true
+ } else {
+ c <- false
+ }
+ case <-prod:
+ if j.Status.Status == lulu.OrderProductionDelayed {
+ setStatus(lulu.OrderInProduction)
+ }
+ case <-ship:
+ if j.Status.Status == lulu.OrderInProduction {
+ setStatus(lulu.OrderShipped)
+ }
+ case <-arrive:
+ if j.Status.Status == lulu.OrderShipped {
+ setStatus(lulu.OrderDelivered)
+ }
+ }
+ }
+ }()
+ return job{jc, cancel}
+}
+
+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()
+
+ id := uint64(len(c.jobs))
+ lineItems := make([]lulu.LineItem, len(items))
+ costItems := make([]lulu.CostLineItem, len(items))
+ for i, it := range items {
+ pid := lulu.PrintableId(crand.Text())
+ lineItems[i] = lulu.LineItem{
+ ExternalId: it.ExternalId,
+ Mfg: it.Mfg,
+ PrintableId: pid,
+ Quantity: it.Quantity,
+ Title: it.Title,
+ }
+ c.printables[pid] = it
+ costItems[i] = lulu.CostLineItem{
+ Mfg: it.Mfg,
+ Quantity: it.Quantity,
+ }
+ }
+ 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) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+
+ id := uint64(len(c.jobs))
+ lineItems := make([]lulu.LineItem, len(items))
+ costItems := make([]lulu.CostLineItem, len(items))
+ for i, it := range items {
+ pid := it.PrintableId
+ p, ok := c.printables[pid]
+ if !ok {
+ return lulu.PrintJob{}, fmt.Errorf("printable %q does not exist", pid)
+ }
+ lineItems[i] = lulu.LineItem{
+ ExternalId: it.ExternalId,
+ Mfg: p.Mfg,
+ PrintableId: pid,
+ Quantity: it.Quantity,
+ Title: it.Title,
+ }
+ costItems[i] = lulu.CostLineItem{
+ Mfg: p.Mfg,
+ Quantity: it.Quantity,
+ }
+ }
+ 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) {
+ cost, addrVal, err := c.Cost(costItems, addr, opt)
+ if err != nil {
+ return lulu.PrintJob{}, err
+ }
+ j := print(id, eid, contact, delay, addr, opt, addrVal, lineItems, cost)
+ c.jobs = append(c.jobs, j)
+ return <-j.j, nil
+}
+
+func (c *mockClient) Job(id uint64) (lulu.PrintJob, error) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ if id < uint64(len(c.jobs)) {
+ return <-c.jobs[id].j, nil
+ }
+ return lulu.PrintJob{}, fmt.Errorf("job %d does not exist", id)
+}
+
+// Ignores queries
+func (c *mockClient) Jobs(qs ...lulu.PrintJobQuery) ([]lulu.PrintJob, error) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ js := make([]lulu.PrintJob, len(c.jobs))
+ for i := range c.jobs {
+ js[i] = <-c.jobs[i].j
+ }
+ return js, nil
+}
+
+func (c *mockClient) Cancel(id uint64) error {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ if id < uint64(len(c.jobs)) {
+ cc := make(chan bool)
+ defer close(cc)
+ c.jobs[id].cancel <- cc
+ if <-cc {
+ return nil
+ } else {
+ return fmt.Errorf("failed to cancel job %d", id)
+ }
+ } else {
+ return fmt.Errorf("job %d does not exist", id)
+ }
+}