package tools import ( "context" "errors" "strings" "testing" "time" "nearle/models" ) const cardSecret = "a-test-signing-key-long-enough" // writingApprovals records every write, so a test can assert that nothing // happened as easily as that something did. type writingApprovals struct { fakeApprovals wrote []int status string err error } func (w *writingApprovals) UpdateStockRequest(requestID int, status string) error { if w.err != nil { return w.err } w.wrote = append(w.wrote, requestID) w.status = status return nil } func pendingRequest(id, qty int, product, branch string) models.StockRequest { return models.StockRequest{ Requestid: id, Qty: qty, Productname: product, Locationname: branch, Status: "Pending", Created: time.Date(2026, 9, 20, 9, 0, 0, 0, time.UTC), } } func approvalSetup(t *testing.T, rows ...models.StockRequest) (*Registry, *writingApprovals, Agent) { t.Helper() t.Setenv("POS_TOKEN_SECRET", cardSecret) store := &writingApprovals{} store.rows = rows r := New(nil) if err := r.Register(ApproveStockRequest(store, store)); err != nil { t.Fatalf("registering: %v", err) } return r, store, Agent{Name: "inventory", Tools: []string{"approve_stock_request"}} } var owner = Caller{Userid: 904, Tenantid: 1147} // propose runs the model's half and returns the card. func propose(t *testing.T, r *Registry, agent Agent, id int, caller Caller) (Proposal, error) { t.Helper() result, err := r.Call(context.Background(), agent, "approve_stock_request", map[string]any{"requestid": id}, caller) if err != nil { return Proposal{}, err } proposal, ok := result.Rows.(Proposal) if !ok { t.Fatalf("a write returned %T, not a proposal", result.Rows) } return proposal, nil } /* ── Nothing auto-executes ─────────────────────────────────────────────── */ func TestAskingForAWriteChangesNothing(t *testing.T) { // The whole phase in one test. The model asked; the database did not move. r, store, agent := approvalSetup(t, pendingRequest(41, 12, "Rice", "R Mart")) proposal, err := propose(t, r, agent, 41, owner) if err != nil { t.Fatalf("proposing: %v", err) } if len(store.wrote) != 0 { t.Fatalf("a write happened on the model's say so: %v", store.wrote) } if proposal.Card == "" { t.Fatal("no card came back, so nothing can be approved") } } func TestTheModelIsToldItHasNotDoneTheThing(t *testing.T) { // Without this it reports the action in the past tense, and the person // believes it. r, _, agent := approvalSetup(t, pendingRequest(41, 12, "Rice", "R Mart")) result, err := r.Call(context.Background(), agent, "approve_stock_request", map[string]any{"requestid": 41}, owner) if err != nil { t.Fatalf("proposing: %v", err) } note := strings.ToLower(result.Note) if !strings.Contains(note, "not been done") || !strings.Contains(note, "do not say it is done") { t.Fatalf("the model was not told to hold off: %q", result.Note) } } func TestAWriteToolCannotBeBuiltWithAPlainHandler(t *testing.T) { // The structural half: there is no shape of Tool a caller can construct // that writes when the model asks for it. r := New(nil) err := r.Register(Tool{ Name: "sneaky", Description: strings.Repeat("a write pretending to be ordinary ", 3), Scope: ScopeWrite, Handler: func(context.Context, Request) (Result, error) { t.Fatal("a hand-built write tool ran") return Result{}, nil }, }) if err == nil { t.Fatal("a write tool was registered without a propose/execute pair") } } /* ── Approving ─────────────────────────────────────────────────────────── */ func TestAnApprovedCardPerformsTheWriteExactlyOnce(t *testing.T) { r, store, agent := approvalSetup(t, pendingRequest(41, 12, "Rice", "R Mart")) proposal, _ := propose(t, r, agent, 41, owner) result, err := r.Approve(context.Background(), agent, proposal.Card, owner) if err != nil { t.Fatalf("approving: %v", err) } if len(store.wrote) != 1 || store.wrote[0] != 41 { t.Fatalf("the write did not happen as expected: %v", store.wrote) } if store.status != "Approved" { t.Fatalf("wrote status %q — the column expects the capitalised word", store.status) } if result.Count != 1 { t.Fatalf("the result does not describe the change: %+v", result) } } func TestAnUnapprovedCardIsTheOnlyWayInAndAForgedOneFails(t *testing.T) { r, store, agent := approvalSetup(t, pendingRequest(41, 12, "Rice", "R Mart")) for _, forged := range []string{ "", "c1.", "c1.rubbish.signature", "not-a-card", "c1.eyJ0IjoiYXBwcm92ZV9zdG9ja19yZXF1ZXN0In0.wrongsignature", } { if _, err := r.Approve(context.Background(), agent, forged, owner); err == nil { t.Fatalf("a forged card was accepted: %q", forged) } } if len(store.wrote) != 0 { t.Fatalf("a forged card wrote something: %v", store.wrote) } } func TestACardIsNotTransferable(t *testing.T) { // Without this, one person's approval could be replayed by another — // including somebody in a different shop. r, store, agent := approvalSetup(t, pendingRequest(41, 12, "Rice", "R Mart")) proposal, _ := propose(t, r, agent, 41, owner) someoneElse := Caller{Userid: 905, Tenantid: 1147} if _, err := r.Approve(context.Background(), agent, proposal.Card, someoneElse); !errors.Is(err, ErrNotYourCard) { t.Fatalf("another user approved somebody else's card: %v", err) } anotherShop := Caller{Userid: 904, Tenantid: 916} if _, err := r.Approve(context.Background(), agent, proposal.Card, anotherShop); !errors.Is(err, ErrNotYourCard) { t.Fatalf("another shop approved this card: %v", err) } if len(store.wrote) != 0 { t.Fatalf("a transferred card wrote something: %v", store.wrote) } } func TestApprovalDoesNotCarryForward(t *testing.T) { // Approving one request is not consent for the next. A second card has to // be resolved and approved on its own. r, store, agent := approvalSetup(t, pendingRequest(41, 12, "Rice", "R Mart"), pendingRequest(42, 3, "Oil", "R Mart")) first, _ := propose(t, r, agent, 41, owner) if _, err := r.Approve(context.Background(), agent, first.Card, owner); err != nil { t.Fatalf("approving: %v", err) } if len(store.wrote) != 1 || store.wrote[0] != 41 { t.Fatalf("approving one request touched another: %v", store.wrote) } } /* ── Re-validated at execution ─────────────────────────────────────────── */ func TestACardApprovedTwiceDoesNotWriteTwice(t *testing.T) { // A signed card carries no nonce. Replay is refused where it belongs — // against the live database, which no longer has the request pending. r, store, agent := approvalSetup(t, pendingRequest(41, 12, "Rice", "R Mart")) proposal, _ := propose(t, r, agent, 41, owner) if _, err := r.Approve(context.Background(), agent, proposal.Card, owner); err != nil { t.Fatalf("first approval: %v", err) } // The request is no longer pending, exactly as the database would report. store.rows = nil if _, err := r.Approve(context.Background(), agent, proposal.Card, owner); err == nil { t.Fatal("the same card wrote a second time") } if len(store.wrote) != 1 { t.Fatalf("the write happened %d times", len(store.wrote)) } } func TestACardExpires(t *testing.T) { // A card resolved against a shop half an hour ago is a card about a shop // that has moved on. r, _, agent := approvalSetup(t, pendingRequest(41, 12, "Rice", "R Mart")) proposal, _ := propose(t, r, agent, 41, owner) r.now = func() time.Time { return time.Now().Add(2 * CardExpiryForTests) } if _, err := r.Approve(context.Background(), agent, proposal.Card, owner); err == nil { t.Fatal("an expired card was approved") } } func TestAFailedWriteIsReportedNotSwallowed(t *testing.T) { r, store, agent := approvalSetup(t, pendingRequest(41, 12, "Rice", "R Mart")) proposal, _ := propose(t, r, agent, 41, owner) store.err = errors.New("the database is down") if _, err := r.Approve(context.Background(), agent, proposal.Card, owner); err == nil { t.Fatal("a failed write reported success") } } /* ── Ownership is re-derived, never compared ───────────────────────────── */ func TestARequestFromAnotherShopIsNotFound(t *testing.T) { // The lookup is scoped to the caller's tenant, so another merchant's id is // simply absent. "Not found" and "not yours" are deliberately the same // message — distinguishing them would make this a way to ask whether an id // exists somewhere else on the platform. r, store, agent := approvalSetup(t, pendingRequest(41, 12, "Rice", "R Mart")) if _, err := propose(t, r, agent, 999, owner); err == nil { t.Fatal("a request that is not this shop's was resolved") } if len(store.wrote) != 0 { t.Fatalf("something was written: %v", store.wrote) } } func TestTheModelCannotNameAShop(t *testing.T) { tool := ApproveStockRequest(&writingApprovals{}, &writingApprovals{}) for _, field := range tool.Schema.Fields { if scopingArguments[strings.ToLower(field.Name)] { t.Fatalf("the write offers %q for the model to set", field.Name) } } } /* ── What the person sees ──────────────────────────────────────────────── */ func TestTheCardShowsWhatWasResolvedNotWhatWasAsked(t *testing.T) { // A person approving "approve this request" must see which request, from // which branch, for what — the sentence and the action are produced by // different things and only one of them is checkable. r, _, agent := approvalSetup(t, pendingRequest(41, 12, "Rice", "R Mart")) proposal, _ := propose(t, r, agent, 41, owner) if !strings.Contains(proposal.Summary, "Rice") || !strings.Contains(proposal.Summary, "R Mart") { t.Fatalf("the summary names nothing checkable: %q", proposal.Summary) } labels := map[string]string{} for _, detail := range proposal.Details { labels[detail.Label] = detail.Value } if labels["Request"] != "#41" { t.Fatalf("the card does not name the request by id: %+v", proposal.Details) } for _, want := range []string{"Product", "Quantity", "Branch"} { if labels[want] == "" { t.Fatalf("the card is missing %q: %+v", proposal.Details, want) } } } func TestAnUnusualQuantityIsFlaggedAndAnOrdinaryOneIsNot(t *testing.T) { // A warning on every card is a warning on none. r, _, agent := approvalSetup(t, pendingRequest(41, 12, "Rice", "R Mart")) ordinary, _ := propose(t, r, agent, 41, owner) if ordinary.Warning != "" { t.Fatalf("an ordinary request was flagged: %q", ordinary.Warning) } r2, _, agent2 := approvalSetup(t, pendingRequest(42, 9000, "Rice", "R Mart")) large, _ := propose(t, r2, agent2, 42, owner) if large.Warning == "" { t.Fatal("nine thousand units passed without comment") } } /* ── The audit trail ───────────────────────────────────────────────────── */ func TestAnApprovalIsRecordedBeforeTheWriteLeaves(t *testing.T) { // A crash mid-write has to leave a trace that it was attempted. A row // written only on success is missing exactly when it is needed. t.Setenv("POS_TOKEN_SECRET", cardSecret) audit := &CollectAudit{} store := &writingApprovals{} store.rows = []models.StockRequest{pendingRequest(41, 12, "Rice", "R Mart")} store.err = errors.New("the database died mid-write") r := New(audit) _ = r.Register(ApproveStockRequest(store, store)) agent := Agent{Name: "inventory", Tools: []string{"approve_stock_request"}} proposal, _ := propose(t, r, agent, 41, owner) _, _ = r.Approve(context.Background(), agent, proposal.Card, owner) var sawApproved, sawFailed bool for _, entry := range audit.Entries { if entry.Outcome == "approved" { sawApproved = true } if entry.Outcome == OutcomeFailed { sawFailed = true } } if !sawApproved { t.Fatal("a write that died left no record that it was attempted") } if !sawFailed { t.Fatal("the failure itself was not recorded") } } func TestARejectedCardLeavesNoSideEffect(t *testing.T) { // Rejection is the console never calling Approve. Nothing to undo, nothing // half-written — and the proposal itself is still in the trail. t.Setenv("POS_TOKEN_SECRET", cardSecret) audit := &CollectAudit{} store := &writingApprovals{} store.rows = []models.StockRequest{pendingRequest(41, 12, "Rice", "R Mart")} r := New(audit) _ = r.Register(ApproveStockRequest(store, store)) agent := Agent{Name: "inventory", Tools: []string{"approve_stock_request"}} if _, err := propose(t, r, agent, 41, owner); err != nil { t.Fatalf("proposing: %v", err) } if len(store.wrote) != 0 { t.Fatalf("an unapproved proposal wrote something: %v", store.wrote) } last, _ := audit.Last() if last.Outcome != "proposed" { t.Fatalf("the proposal is not in the trail: %+v", last) } } /* ── The allow-list still applies at approval ──────────────────────────── */ func TestACardCannotOutliveAnAgentLosingTheTool(t *testing.T) { r, store, agent := approvalSetup(t, pendingRequest(41, 12, "Rice", "R Mart")) proposal, _ := propose(t, r, agent, 41, owner) narrowed := Agent{Name: "inventory", Tools: []string{"low_stock"}} if _, err := r.Approve(context.Background(), narrowed, proposal.Card, owner); !errors.Is(err, ErrNotAllowed) { t.Fatalf("a card was a way around the allow-list: %v", err) } if len(store.wrote) != 0 { t.Fatalf("something was written: %v", store.wrote) } }