package tools import ( "context" "errors" "testing" "nearle/models" ) type fakeBranches struct { rows []models.Ordersummarylocation err error seen int } func (f *fakeBranches) GetLocationOrderSummary(tenantID int) ([]models.Ordersummarylocation, error) { f.seen = tenantID return f.rows, f.err } func runBranches(t *testing.T, rows []models.Ordersummarylocation, caller Caller) (Result, *fakeBranches) { t.Helper() branches := &fakeBranches{rows: rows} r := New(nil) if err := r.Register(BranchPerformance(branches)); err != nil { t.Fatalf("registering: %v", err) } result, err := r.Call(context.Background(), Agent{Name: "orders", Tools: []string{"branch_performance"}}, "branch_performance", nil, caller) if err != nil { t.Fatalf("calling: %v", err) } return result, branches } func branchRows(t *testing.T, result Result) []BranchRow { t.Helper() rows, ok := result.Rows.([]BranchRow) if !ok { t.Fatalf("rows are not branches: %T", result.Rows) } return rows } func branch(id int, name string, total, delivered, cancelled int) models.Ordersummarylocation { return models.Ordersummarylocation{ Locationid: id, Locationname: name, Total: total, Delivered: delivered, Cancelled: cancelled, } } /* ── The tenant is the session's ───────────────────────────────────────── */ func TestBranchPerformanceReadsTheSessionsTenant(t *testing.T) { _, branches := runBranches(t, nil, Caller{Userid: 904, Tenantid: 1147}) if branches.seen != 1147 { t.Fatalf("read tenant %d", branches.seen) } } /* ── The arithmetic ────────────────────────────────────────────────────── */ func TestTheCancelRateIsComputedHereNotByTheModel(t *testing.T) { // A model asked to divide two numbers in a sentence usually gets it right // and sometimes does not, and the answer looks the same either way. result, _ := runBranches(t, []models.Ordersummarylocation{branch(1, "R Mart", 200, 150, 30)}, merchantCaller) row := branchRows(t, result)[0] if row.CancelRate != 15 { t.Fatalf("30 of 200 reported as %.1f%%", row.CancelRate) } if row.DeliveryRate != 75 { t.Fatalf("150 of 200 reported as %.1f%%", row.DeliveryRate) } if row.Outstanding != 20 { t.Fatalf("outstanding is %d, not 20", row.Outstanding) } } func TestABranchWithNoOrdersHasNoRate(t *testing.T) { // 0% would read as "nothing is cancelled here", which is a claim about // performance rather than the absence of any. result, _ := runBranches(t, []models.Ordersummarylocation{branch(2, "New Shop", 0, 0, 0)}, merchantCaller) row := branchRows(t, result)[0] if row.CancelRate != 0 || row.Orders != 0 { t.Fatalf("an empty branch got a rate: %+v", row) } if row.Note == "" { t.Fatal("an empty branch does not say why it has no numbers") } } /* ── Standing out is relative ──────────────────────────────────────────── */ func TestABranchIsJudgedAgainstItsOwnBusinessNotAFixedNumber(t *testing.T) { // 9% is poor in a business averaging 3% and unremarkable in one averaging // 11%. A fixed threshold flags every branch of the second or none of the // first. tight, _ := runBranches(t, []models.Ordersummarylocation{ branch(1, "Good", 100, 97, 2), branch(2, "Bad", 100, 88, 9), }, merchantCaller) loose, _ := runBranches(t, []models.Ordersummarylocation{ branch(1, "One", 100, 85, 12), branch(2, "Two", 100, 87, 9), }, merchantCaller) flaggedTight := 0 for _, row := range branchRows(t, tight) { if row.Note != "" { flaggedTight++ } } flaggedLoose := 0 for _, row := range branchRows(t, loose) { if row.Note != "" { flaggedLoose++ } } if flaggedTight == 0 { t.Fatal("9% against a 5.5% average was not worth mentioning") } if flaggedLoose > 0 { t.Fatal("9% against a 10.5% average was flagged as standing out") } } func TestTheWorstBranchIsFirst(t *testing.T) { result, _ := runBranches(t, []models.Ordersummarylocation{ branch(1, "Fine", 100, 95, 2), branch(2, "Poor", 100, 80, 18), branch(3, "Middling", 100, 90, 7), }, merchantCaller) if got := branchRows(t, result)[0].Branch; got != "Poor" { t.Fatalf("the worst branch is not first: %q", got) } } func TestTheBusinessAverageIsStatedForTheModelToCompareAgainst(t *testing.T) { // Without it a model reports "15% cancelled" with no idea whether that is // alarming or ordinary for this shop. result, _ := runBranches(t, []models.Ordersummarylocation{ branch(1, "One", 100, 90, 10), branch(2, "Two", 100, 80, 20), }, merchantCaller) if result.Note == "" { t.Fatal("the answer carries no business-wide figure") } } /* ── Failure ───────────────────────────────────────────────────────────── */ func TestABrokenBranchReadIsAnErrorNotAnEmptyShop(t *testing.T) { // An empty list would be reported as "you have no branches", which is a // statement about the business rather than about the database. branches := &fakeBranches{err: errors.New("the database is down")} r := New(nil) _ = r.Register(BranchPerformance(branches)) _, err := r.Call(context.Background(), Agent{Name: "orders", Tools: []string{"branch_performance"}}, "branch_performance", nil, merchantCaller) if err == nil { t.Fatal("a failed read was reported as a successful answer") } } func TestStaffMustPickATenantForBranchPerformance(t *testing.T) { branches := &fakeBranches{} r := New(nil) _ = r.Register(BranchPerformance(branches)) _, err := r.Call(context.Background(), Agent{Name: "orders", Tools: []string{"branch_performance"}}, "branch_performance", nil, Caller{Userid: 12, Superadmin: true}) if err == nil { t.Fatal("staff compared branches across every tenant at once") } } /* ── percent ───────────────────────────────────────────────────────────── */ func TestPercentGuardsTheZeroDenominator(t *testing.T) { // NaN serialises as null, which a model reads as "no data" rather than // "no orders". if got := percent(0, 0); got != 0 { t.Fatalf("0 of 0 is %v", got) } if got := percent(1, 3); got != 33.3 { t.Fatalf("1 of 3 rounded to %v", got) } if got := percent(2, 3); got != 66.7 { t.Fatalf("2 of 3 rounded to %v", got) } }