agent
This commit is contained in:
110
services/assistantAudit_test.go
Normal file
110
services/assistantAudit_test.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"nearle/models"
|
||||
"nearle/repositories"
|
||||
"nearle/services/tools"
|
||||
)
|
||||
|
||||
type recordingAudit struct {
|
||||
rows []models.AssistantAudit
|
||||
err error
|
||||
}
|
||||
|
||||
func (r *recordingAudit) Record(entry models.AssistantAudit) error {
|
||||
if r.err != nil {
|
||||
return r.err
|
||||
}
|
||||
r.rows = append(r.rows, entry)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *recordingAudit) Recent(int, int) ([]models.AssistantAudit, error) { return r.rows, nil }
|
||||
|
||||
func TestAnAuditRowKeepsWhatTheCallActuallyDid(t *testing.T) {
|
||||
repo := &recordingAudit{}
|
||||
sink := NewDBAudit(repo)
|
||||
|
||||
sink.Write(context.Background(), tools.AuditEntry{
|
||||
At: time.Now(), Agent: "orders", Tool: "stuck_orders", Scope: "read",
|
||||
Userid: 904, Tenantid: 1147, Outcome: tools.OutcomeOK, Rows: 3,
|
||||
Args: map[string]any{"minutes_waiting": 30}, Took: 12 * time.Millisecond,
|
||||
})
|
||||
|
||||
if len(repo.rows) != 1 {
|
||||
t.Fatalf("wrote %d rows", len(repo.rows))
|
||||
}
|
||||
row := repo.rows[0]
|
||||
if row.Tool != "stuck_orders" || row.Tenantid != 1147 || row.Rows != 3 {
|
||||
t.Fatalf("the row does not describe the call: %+v", row)
|
||||
}
|
||||
if row.Tookms != 12 {
|
||||
t.Fatalf("duration stored as %d", row.Tookms)
|
||||
}
|
||||
}
|
||||
|
||||
func TestARefusalIsKept(t *testing.T) {
|
||||
// The interesting rows. A trail of successes answers "did anything try to
|
||||
// read another tenant?" with silence, which reads the same as "no".
|
||||
repo := &recordingAudit{}
|
||||
NewDBAudit(repo).Write(context.Background(), tools.AuditEntry{
|
||||
Agent: "orders", Tool: "stuck_orders", Outcome: tools.OutcomeRefused,
|
||||
Detail: "no tenant on the caller", Userid: 904,
|
||||
})
|
||||
|
||||
if len(repo.rows) != 1 || repo.rows[0].Outcome != tools.OutcomeRefused {
|
||||
t.Fatalf("a refusal was not recorded: %+v", repo.rows)
|
||||
}
|
||||
if repo.rows[0].Detail == "" {
|
||||
t.Fatal("the refusal does not say why")
|
||||
}
|
||||
}
|
||||
|
||||
func TestALostAuditRowDoesNotTakeTheAssistantDown(t *testing.T) {
|
||||
// A full disk must not stop a merchant asking where their orders are. The
|
||||
// failure is logged; it is not allowed to become an error the caller sees.
|
||||
repo := &recordingAudit{err: errors.New("disk is full")}
|
||||
NewDBAudit(repo).Write(context.Background(), tools.AuditEntry{Tool: "stuck_orders"})
|
||||
// Reaching here without a panic is the assertion.
|
||||
}
|
||||
|
||||
func TestAnAuditSinkWithNoDatabaseStillLogs(t *testing.T) {
|
||||
// A deployment whose migration has not run yet keeps working.
|
||||
NewDBAudit(nil).Write(context.Background(), tools.AuditEntry{Tool: "stuck_orders"})
|
||||
}
|
||||
|
||||
func TestArgumentsAreStoredInAStableOrder(t *testing.T) {
|
||||
// Go randomises map iteration. Without sorting, the same call stores
|
||||
// different JSON every time and a query looking for one of them finds one
|
||||
// of them.
|
||||
args := map[string]any{"zulu": 1, "alpha": "two", "mike": true}
|
||||
first := repositories.EncodeAuditArgs(args)
|
||||
for range 20 {
|
||||
if got := repositories.EncodeAuditArgs(args); got != first {
|
||||
t.Fatalf("two encodings differ:\n%s\n%s", first, got)
|
||||
}
|
||||
}
|
||||
if first != `{"alpha":"two","mike":true,"zulu":1}` {
|
||||
t.Fatalf("unexpected encoding: %s", first)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoArgumentsIsAnEmptyObjectNotEmptyText(t *testing.T) {
|
||||
// `""` is not valid jsonb and would fail the insert, losing the row.
|
||||
if got := repositories.EncodeAuditArgs(nil); got != "{}" {
|
||||
t.Fatalf("no arguments encoded as %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnUnencodableArgumentDoesNotLoseTheRow(t *testing.T) {
|
||||
// Losing the whole audit row to save one bad argument is the wrong trade.
|
||||
got := repositories.EncodeAuditArgs(map[string]any{"bad": make(chan int)})
|
||||
if got == "" {
|
||||
t.Fatal("an unencodable argument produced no JSON at all")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user