159 lines
5.7 KiB
Go
159 lines
5.7 KiB
Go
package tools
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"nearle/models"
|
|
)
|
|
|
|
var channelNow = time.Date(2026, 9, 23, 15, 0, 0, 0, time.Local)
|
|
|
|
type fakeRevenue struct {
|
|
summary *models.TenantRevenueSummary
|
|
err error
|
|
seenFrom string
|
|
seenTo string
|
|
seenLid int
|
|
seenTid int
|
|
}
|
|
|
|
func (f *fakeRevenue) GetRevenueSummary(tid, lid int, fdate, tdate string) (*models.TenantRevenueSummary, error) {
|
|
f.seenTid, f.seenLid, f.seenFrom, f.seenTo = tid, lid, fdate, tdate
|
|
return f.summary, f.err
|
|
}
|
|
|
|
type fakeCounter struct {
|
|
summary *models.PosSalesSummary
|
|
err error
|
|
seen models.PosSalesFilter
|
|
}
|
|
|
|
func (f *fakeCounter) SalesSummary(filter models.PosSalesFilter) (*models.PosSalesSummary, error) {
|
|
f.seen = filter
|
|
return f.summary, f.err
|
|
}
|
|
|
|
var atBranch = Caller{Userid: 904, Tenantid: 1147, Locationid: 1172}
|
|
|
|
func channelTool(revenue *fakeRevenue, counter *fakeCounter) Tool {
|
|
return SalesByChannel(revenue, counter, func() time.Time { return channelNow })
|
|
}
|
|
|
|
/* ── The model never sends a date ──────────────────────────────────────── */
|
|
|
|
func TestTheDateRangeIsComputedHereNotSentByTheModel(t *testing.T) {
|
|
// A format, a timezone, or an off-by-one on "last week" each produce a
|
|
// plausible figure for the wrong period — undetectable in a sentence.
|
|
revenue, counter := &fakeRevenue{}, &fakeCounter{}
|
|
if _, err := call1(t, channelTool(revenue, counter), nil, atBranch); err != nil {
|
|
t.Fatalf("calling: %v", err)
|
|
}
|
|
|
|
// Seven days ending today, inclusive.
|
|
if revenue.seenFrom != "2026-09-17" || revenue.seenTo != "2026-09-23" {
|
|
t.Fatalf("range was %s to %s", revenue.seenFrom, revenue.seenTo)
|
|
}
|
|
if counter.seen.Fromdate != revenue.seenFrom || counter.seen.Todate != revenue.seenTo {
|
|
t.Fatalf("the two ledgers were read over different periods: %+v", counter.seen)
|
|
}
|
|
}
|
|
|
|
func TestTheToolHasNoDateArgumentAtAll(t *testing.T) {
|
|
tool := SalesByChannel(&fakeRevenue{}, &fakeCounter{}, nil)
|
|
for _, field := range tool.Schema.Fields {
|
|
if strings.Contains(field.Name, "date") || strings.Contains(field.Name, "from") {
|
|
t.Fatalf("the schema offers %q for the model to fill in", field.Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestThePeriodCanBeWidened(t *testing.T) {
|
|
revenue := &fakeRevenue{}
|
|
if _, err := call1(t, channelTool(revenue, &fakeCounter{}), map[string]any{"days": 30}, atBranch); err != nil {
|
|
t.Fatalf("calling: %v", err)
|
|
}
|
|
if revenue.seenFrom != "2026-08-25" {
|
|
t.Fatalf("thirty days back was %s", revenue.seenFrom)
|
|
}
|
|
}
|
|
|
|
/* ── Two ledgers, never one total ──────────────────────────────────────── */
|
|
|
|
func TestBothChannelsComeBackSeparately(t *testing.T) {
|
|
revenue := &fakeRevenue{summary: &models.TenantRevenueSummary{OverallRevenue: 41250}}
|
|
counter := &fakeCounter{summary: &models.PosSalesSummary{Grosssales: 18300, Billcount: 92}}
|
|
|
|
result, err := call1(t, channelTool(revenue, counter), nil, atBranch)
|
|
if err != nil {
|
|
t.Fatalf("calling: %v", err)
|
|
}
|
|
|
|
rows, ok := result.Rows.([]ChannelTotals)
|
|
if !ok || len(rows) != 2 {
|
|
t.Fatalf("expected two channels: %+v", result.Rows)
|
|
}
|
|
if rows[0].Revenue != 41250 || rows[1].Revenue != 18300 {
|
|
t.Fatalf("the figures did not survive: %+v", rows)
|
|
}
|
|
if rows[0].Ledger == rows[1].Ledger {
|
|
t.Fatal("both channels claim the same ledger")
|
|
}
|
|
}
|
|
|
|
func TestTheAnswerForbidsAddingTheTwoTogether(t *testing.T) {
|
|
// The most useful-looking wrong number available on this page.
|
|
revenue := &fakeRevenue{summary: &models.TenantRevenueSummary{OverallRevenue: 100}}
|
|
counter := &fakeCounter{summary: &models.PosSalesSummary{Grosssales: 50}}
|
|
|
|
result, _ := call1(t, channelTool(revenue, counter), nil, atBranch)
|
|
if !strings.Contains(strings.ToLower(result.Note), "do not add") {
|
|
t.Fatalf("nothing stops the model reporting one total: %q", result.Note)
|
|
}
|
|
}
|
|
|
|
func TestTheCounterFigureSaysItIsGross(t *testing.T) {
|
|
counter := &fakeCounter{summary: &models.PosSalesSummary{Grosssales: 50, Billcount: 3}}
|
|
result, _ := call1(t, channelTool(&fakeRevenue{}, counter), nil, atBranch)
|
|
|
|
rows := result.Rows.([]ChannelTotals)
|
|
if rows[len(rows)-1].Caveat == "" {
|
|
t.Fatal("a gross figure is presented as comparable without saying so")
|
|
}
|
|
}
|
|
|
|
/* ── Scope ─────────────────────────────────────────────────────────────── */
|
|
|
|
func TestChannelsRefuseAnAllBranchesQuestion(t *testing.T) {
|
|
// The counter side is readable one outlet at a time. Answering with the app
|
|
// half alone would be presented as the whole comparison.
|
|
_, err := call1(t, channelTool(&fakeRevenue{}, &fakeCounter{}), nil, merchantCaller)
|
|
if err == nil {
|
|
t.Fatal("an all-branches comparison was answered")
|
|
}
|
|
}
|
|
|
|
func TestTheBranchComesFromTheSession(t *testing.T) {
|
|
revenue := &fakeRevenue{}
|
|
if _, err := call1(t, channelTool(revenue, &fakeCounter{}), nil, atBranch); err != nil {
|
|
t.Fatalf("calling: %v", err)
|
|
}
|
|
if revenue.seenLid != 1172 {
|
|
t.Fatalf("read branch %d", revenue.seenLid)
|
|
}
|
|
}
|
|
|
|
/* ── Failure ───────────────────────────────────────────────────────────── */
|
|
|
|
func TestAHalfAnsweredComparisonIsAnErrorNotHalfAnAnswer(t *testing.T) {
|
|
// One channel returned alone would be read as "the counter took nothing".
|
|
revenue := &fakeRevenue{summary: &models.TenantRevenueSummary{OverallRevenue: 100}}
|
|
counter := &fakeCounter{err: errors.New("pos database is down")}
|
|
|
|
if _, err := call1(t, channelTool(revenue, counter), nil, atBranch); err == nil {
|
|
t.Fatal("a failed counter read produced a one-sided comparison")
|
|
}
|
|
}
|