1package e2e23import (4 "encoding/json"5 "net/http"6 "net/http/httptest"7 "strings"8 "sync/atomic"9 "testing"10)1112// fakeTypeSafe stands in for the TypeSafe API: yes/no questions get 0.9,13// choices pick the last label, scores get 1.5.14func fakeTypeSafe(t *testing.T) (*httptest.Server, *atomic.Int32) {15 t.Helper()16 var requests atomic.Int3217 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {18 if r.URL.Path != "/v1/systemone" || r.Header.Get("Authorization") != "Bearer test-key" {19 http.Error(w, "unexpected request", http.StatusBadRequest)20 return21 }22 requests.Add(1)23 var req struct {24 Questions map[string]struct {25 Type string `json:"type"`26 Criteria json.RawMessage `json:"criteria"`27 } `json:"questions"`28 }29 json.NewDecoder(r.Body).Decode(&req)30 q := req.Questions["answer"]31 var answer map[string]any32 switch q.Type {33 case "noul":34 answer = map[string]any{"type": "noul", "noul": 0.9}35 case "choice":36 var labels map[string]any37 json.Unmarshal(q.Criteria, &labels)38 probs, last := map[string]float64{}, ""39 for l := range labels {40 probs[l] = 041 if l > last {42 last = l43 }44 }45 probs[last] = 146 answer = map[string]any{"type": "choice", "choice": last, "confidence": 0.8, "probabilities": probs}47 default:48 answer = map[string]any{"type": "score", "score": 1.5, "confidence": 0.6,49 "legend": map[string]any{"0": "low", "1": "mid", "2": "high"},50 "probabilities": map[string]float64{"0": 0.2, "1": 0.3, "2": 0.5}}51 }52 json.NewEncoder(w).Encode(map[string]any{53 "model": "jev-latest", "answers": map[string]any{"answer": answer},54 "usage": map[string]int{"input_tokens": 1, "output_tokens": 1},55 })56 }))57 t.Cleanup(srv.Close)58 return srv, &requests59}6061func TestJEVFunctionsAgainstFakeService(t *testing.T) {62 t.Parallel()63 srv, requests := fakeTypeSafe(t)64 s := startWith(t, options{env: []string{"TYPESAFE_API_KEY=test-key", "TYPESAFE_BASE_URL=" + srv.URL}})65 s.keys("The box arrived crushed", "<enter>")66 s.keys(`=JEV.TEST(A1, "Is this a complaint?")`, "<enter>")67 s.keys(`=JEV.PROB(A1, "Is this a complaint?")`, "<enter>")68 s.keys(`=JEV.CLASSIFY(A1, "Sentiment", "negative, positive")`, "<enter>")69 s.keys(`=JEV.SCORE(A1, "Urgency", "low, mid, high")`, "<enter>")70 s.waitForLine(gridRow1+1, " 2 TRUE")71 s.waitForLine(gridRow1+2, numRow(3, "90%"))72 s.waitForLine(gridRow1+3, " 4 positive")73 s.waitForLine(gridRow1+4, numRow(5, "1.5"))74 // TEST and PROB asked the same question: three requests, not four.75 if n := requests.Load(); n != 3 {76 t.Errorf("%d requests", n)77 }78 s.keys("<up>")79 s.waitFor("JEV: score 1.5 on 0 to 2, 60% confident")80}8182func TestJEVOffWithoutKey(t *testing.T) {83 t.Parallel()84 s := start(t, "")85 s.keys(`=JEV.TEST("x", "Q")`, "<enter>", "<up>")86 s.waitFor("JEV functions need an API key")87 if !strings.Contains(s.line(gridRow1), "#N/A") {88 t.Errorf("A1 shows %q", s.line(gridRow1))89 }90}