diff options
| -rw-r--r-- | lulutest/mock.go | 289 |
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) + } +} |