eleven / ocho Public

Clone
git clone https://amadan.net/eleven/ocho

Plain git — no account needed to clone.

Download

Download this file

1package main
2
3import (
4 "context"
5 "encoding/json"
6 "io"
7 "net/http"
8 "net/http/httptest"
9 "strings"
10 "sync"
11 "testing"
12 "time"
13
14 "lchat/internal/bot"
15 "lchat/internal/e2ee"
16)
17
18// TestServiceOpEndToEnd drives the interactive write flow of
19// docs/integrations.md against the real router: Owner asks Eleven to register
20// a domain → the (scripted) brain proposes a service_op chip whose label is
21// the manifest's rendered confirm line → the member's "tap" seals a one-shot
22// WRITE grant to Eleven's key and posts the connect.op → the sidecar's pinned
23// executor runs the op against the (fake) registrar → the connect.result
24// lands back in the DM. Then the negative: the same op under a READ grant is
25// refused without the registrar ever seeing a second request.
26func TestServiceOpEndToEnd(t *testing.T) {
27 // The fake registrar records every request the executor makes.
28 type regReq struct {
29 method, path, auth, body string
30 }
31 var regMu sync.Mutex
32 var regReqs []regReq
33 fake := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
34 raw, _ := io.ReadAll(r.Body)
35 regMu.Lock()
36 regReqs = append(regReqs, regReq{r.Method, r.URL.Path, r.Header.Get("Authorization"), string(raw)})
37 regMu.Unlock()
38 w.WriteHeader(http.StatusCreated)
39 w.Write([]byte(`{"data":{"id":1,"name":"blip.com"}}`))
40 }))
41 defer fake.Close()
42 t.Setenv("LCHAT_INTEGRATIONS_BASE_DNSIMPLE", fake.URL)
43
44 claude := newFakeAnthropic(t, "Here's the button — it costs real money, so it's your tap.",
45 map[string]any{"name": "service_op", "input": map[string]any{
46 "service": "dnsimple", "op": "domain.register",
47 "args": map[string]any{"account": "123", "domain": "blip.com", "registrant_id": "77"},
48 }})
49 t.Setenv("ANTHROPIC_BASE_URL", claude.srv.URL)
50
51 ts, s := newTestServer(t)
52 owner := provisionNamed(t, ts.URL, "Owner", false)
53 eleven := provisionNamed(t, ts.URL, "Eleven", true)
54 dmID, dmKey := createThreadKind(t, ts.URL, s, "dm", owner, eleven)
55
56 dir := t.TempDir()
57 sessionPath := dir + "/eleven.session.json"
58 if err := eleven.Save(sessionPath); err != nil {
59 t.Fatal(err)
60 }
61 ready := make(chan struct{})
62 ctx, cancel := context.WithCancel(context.Background())
63 defer cancel()
64 eng := &bot.Engine{Base: ts.URL, Session: eleven, SessionPath: sessionPath,
65 Recipe: bot.DefaultRecipe(), AIKey: "test-key",
66 Ready: func() { close(ready) }, Logf: t.Logf}
67 go eng.Run(ctx)
68 awaitReady(t, ready)
69
70 postAs(t, ts.URL, owner, dmID, dmKey, "@Eleven register blip.com on dnsimple, account 123, registrant 77")
71
72 // The reply carries the write chip, its label the manifest's confirm line.
73 waitFor(t, "service_op chip", func() bool { return len(botReplies(t, s, dmID, dmKey, "Eleven")) > 0 })
74 replies := botReplies(t, s, dmID, dmKey, "Eleven")
75 if len(replies[0].Actions) != 1 {
76 t.Fatalf("want the service_op chip, got %+v", replies[0].Actions)
77 }
78 chip := replies[0].Actions[0]
79 if chip["type"] != "service_op" || chip["access"] != "write" || chip["service"] != "dnsimple" || chip["op"] != "domain.register" {
80 t.Fatalf("bad chip: %+v", chip)
81 }
82 if chip["label"] != "Register blip.com? This buys the domain and costs real money." {
83 t.Fatalf("chip label isn't the rendered confirm line: %q", chip["label"])
84 }
85 args, _ := chip["args"].(map[string]any)
86 if args["domain"] != "blip.com" || args["account"] != "123" || args["registrant_id"] != "77" {
87 t.Fatalf("chip args: %+v", args)
88 }
89 regMu.Lock()
90 if len(regReqs) != 0 {
91 t.Fatalf("proposal alone reached the registrar: %+v", regReqs)
92 }
93 regMu.Unlock()
94
95 // The tap: shell.js seals a one-shot WRITE grant to the bot's key and posts
96 // connect.op with the chip's args (requestOp).
97 tap := func(rid, access string) {
98 grant, err := e2ee.SealForPub(eleven.UserPub, map[string]any{
99 "service": "dnsimple", "token": "scoped-registrar-tok",
100 "exp": time.Now().Add(2 * time.Minute).UnixMilli(), "access": access,
101 })
102 if err != nil {
103 t.Fatal(err)
104 }
105 ownerUID, _ := s.db.deviceTokenUser(owner.Token)
106 postTyped(t, ts.URL, owner, dmID, dmKey, map[string]any{
107 "type": "connect.op", "service": "dnsimple", "op": "domain.register",
108 "args": map[string]any{"account": "123", "domain": "blip.com", "registrant_id": "77"},
109 "grant": grant, "rid": rid, "uid": ownerUID, "name": "Owner", "ts": time.Now().UnixMilli(),
110 })
111 }
112 tap("rid-write", "write")
113
114 waitFor(t, "connect.result", func() bool {
115 p := typedReply(t, s, dmID, dmKey, "connect.result")
116 return p != nil && p["rid"] == "rid-write"
117 })
118 res := typedReply(t, s, dmID, dmKey, "connect.result")
119 if res["error"] != nil {
120 t.Fatalf("write op errored: %v", res["error"])
121 }
122 var data struct {
123 Data struct {
124 Name string `json:"name"`
125 } `json:"data"`
126 }
127 if body, _ := res["data"].(string); json.Unmarshal([]byte(body), &data) != nil || data.Data.Name != "blip.com" {
128 t.Fatalf("result data: %v", res["data"])
129 }
130 regMu.Lock()
131 if len(regReqs) != 1 {
132 t.Fatalf("registrar saw %d requests, want 1: %+v", len(regReqs), regReqs)
133 }
134 got := regReqs[0]
135 regMu.Unlock()
136 if got.method != "POST" || got.path != "/v2/123/registrar/domains/blip.com/registrations" {
137 t.Fatalf("registrar request: %s %s", got.method, got.path)
138 }
139 if got.auth != "Bearer scoped-registrar-tok" {
140 t.Fatalf("registrar auth: %q", got.auth)
141 }
142 if !strings.Contains(got.body, `"registrant_id":"77"`) {
143 t.Fatalf("registrar body: %q", got.body)
144 }
145
146 // The negative: the same op under a READ grant must be refused, and the
147 // registrar must never see a second request.
148 tap("rid-read", "read")
149 waitFor(t, "read-grant refusal", func() bool {
150 msgs, err := s.db.messagesAfter(dmID, 0, 200)
151 if err != nil {
152 return false
153 }
154 for _, m := range msgs {
155 var p map[string]any
156 if e2ee.DecryptJSON(dmKey, m.Blob, &p) == nil && p["type"] == "connect.result" && p["rid"] == "rid-read" {
157 e, _ := p["error"].(string)
158 return strings.Contains(e, "read-only")
159 }
160 }
161 return false
162 })
163 regMu.Lock()
164 defer regMu.Unlock()
165 if len(regReqs) != 1 {
166 t.Fatalf("read grant reached the registrar: %+v", regReqs)
167 }
168}
169