cat / 012
clone

a spreadsheet for the terminal: lotus 1-2-3 looks, google sheets keys and formulas, one go binary

cat/module-path
012 / internal / jev / jev_test.go
jev_test.go 4.6K
1package jev23import (4 "context"5 "errors"6 "fmt"7 "strings"8 "testing"910 typesafe "github.com/FelineStateMachine/typesafe-go"1112 "src.rest/cat/012/internal/sheet"13)1415func TestCacheQueuesOnce(t *testing.T) {16 m := NewCache()17 q := sheet.RemoteCall{Kind: "noul", State: "x", Instructions: "Q", Criteria: [2]string{}}18 for range 3 {19 if _, ok := m.Lookup(q); ok {20 t.Fatal("answered before asking")21 }22 }23 if in, queued := m.Busy(); in != 0 || queued != 1 {24 t.Fatalf("busy %d %d: duplicate questions queued", in, queued)25 }26 taken := m.Take(4)27 if len(taken) != 1 {28 t.Fatalf("took %d", len(taken))29 }30 m.Lookup(q) // asking again while in flight doesn't re-queue31 if in, queued := m.Busy(); in != 1 || queued != 0 {32 t.Fatalf("busy %d %d", in, queued)33 }34 m.Store(q, sheet.RemoteAnswer{Noul: 0.7})35 if a, ok := m.Lookup(q); !ok || a.Noul != 0.7 {36 t.Errorf("after store: %+v %v", a, ok)37 }38 m.Forget([]sheet.RemoteCall{q})39 if _, ok := m.Lookup(q); ok {40 t.Error("forgotten answer still there")41 }42 if _, queued := m.Busy(); queued != 1 {43 t.Error("forgotten question not re-queued")44 }45}4647type fakeClient struct {48 resp *typesafe.SystemOneResponse49 err error50 got typesafe.SystemOneRequest51}5253func (f *fakeClient) SystemOne(_ context.Context, r typesafe.SystemOneRequest) (*typesafe.SystemOneResponse, error) {54 f.got = r55 return f.resp, f.err56}5758func TestAsk(t *testing.T) {59 answers := func(a typesafe.Answer) *typesafe.SystemOneResponse {60 return &typesafe.SystemOneResponse{Answers: map[string]typesafe.Answer{"answer": a}}61 }62 tests := []struct {63 call sheet.RemoteCall64 resp *typesafe.SystemOneResponse65 want sheet.RemoteAnswer66 desc string67 }{68 {sheet.RemoteCall{Kind: "noul", State: 42.0, Instructions: "Q", Criteria: [2]string{"big", ""}},69 answers(typesafe.NoulAnswer{Noul: 0.25}), sheet.RemoteAnswer{Noul: 0.25}, "JEV: 25% likely yes"},70 {sheet.RemoteCall{Kind: "choice", State: "x", Instructions: "Q", Criteria: map[string]string{"a": "A", "b": "B"}},71 answers(typesafe.ChoiceAnswer{Choice: "b", Confidence: 0.9}), sheet.RemoteAnswer{Choice: "b", Confidence: 0.9}, "JEV: b, 90% confident"},72 {sheet.RemoteCall{Kind: "score", State: "x", Instructions: "Q", Criteria: []string{"low", "mid", "high"}},73 answers(typesafe.ScoreAnswer{Score: 1.5, Confidence: 0.6}), sheet.RemoteAnswer{Score: 1.5, Confidence: 0.6}, "JEV: score 1.5 on 0 to 2, 60% confident"},74 {sheet.RemoteCall{Kind: "score", State: "x", Instructions: "Q", Criteria: []string{"low", "high"}},75 answers(typesafe.NoulAnswer{Noul: 1}), sheet.RemoteAnswer{Failed: "x"}, ""}, // wrong answer type76 }77 for _, tt := range tests {78 c := &fakeClient{resp: tt.resp}79 got := Ask(context.Background(), c, tt.call)80 if tt.want.Failed != "" {81 if got.Failed == "" {82 t.Errorf("%s: expected a failure, got %+v", tt.call.Kind, got)83 }84 continue85 }86 if got != tt.want {87 t.Errorf("%s: %+v, want %+v", tt.call.Kind, got, tt.want)88 }89 if d := Describe(tt.call, got); d != tt.desc {90 t.Errorf("%s: describe %q, want %q", tt.call.Kind, d, tt.desc)91 }92 // State always travels as an object, which the API requires.93 if st, ok := c.got.State.(map[string]any); !ok || st["value"] != tt.call.State {94 t.Errorf("%s: state %#v", tt.call.Kind, c.got.State)95 }96 }97 // Noul criteria fill in the missing side.98 c := &fakeClient{resp: answers(typesafe.NoulAnswer{})}99 Ask(context.Background(), c, tests[0].call)100 if q := c.got.Questions["answer"].(typesafe.NoulQuestion); q.Criteria.True != "big" || q.Criteria.False != "No." {101 t.Errorf("noul criteria %+v", q.Criteria)102 }103 // Network failures become answers with Failed set.104 if a := Ask(context.Background(), &fakeClient{err: errors.New("offline")}, tests[0].call); a.Failed != "offline" {105 t.Errorf("failure %+v", a)106 }107}108109// The engine rejects oversized questions itself; its limits must match110// the SDK's, which match the service.111func TestLimitsMatchSDK(t *testing.T) {112 s := sheet.New()113 s.SetRemote(NewCache())114 labels := func(n int) string {115 out := make([]string, n)116 for i := range out {117 out[i] = fmt.Sprint("l", i)118 }119 return strings.Join(out, ",")120 }121 for _, tt := range []struct {122 fn string123 max int124 }{{"JEV.CLASSIFY", typesafe.MaxChoiceOptions}, {"JEV.SCORE", typesafe.MaxScoreLevels}} {125 at := sheet.Addr{}126 s.Set(at, fmt.Sprintf(`=%s("x", "q", "%s")`, tt.fn, labels(tt.max)))127 if !sheet.IsPending(s.Value(at)) {128 t.Errorf("%s with %d options rejected: %+v", tt.fn, tt.max, s.Value(at))129 }130 s.Set(at, fmt.Sprintf(`=%s("x", "q", "%s")`, tt.fn, labels(tt.max+1)))131 if v := s.Value(at); v != sheet.ErrValue {132 t.Errorf("%s with %d options = %+v, want #VALUE!", tt.fn, tt.max+1, v)133 }134 }135}