| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "testing" |
| 5 | "time" |
| 6 | ) |
| 7 | |
| 8 | // TestRoutedAttachAnnouncesIdentityWhileConnecting pins the history-first half |
| 9 | // of the attach publication: an attach that commits a session route announces |
| 10 | // that tab while it is still connecting, so the frontend can read the session's |
| 11 | // persisted window before /resume activates the remote runtime. The other half |
| 12 | // — a routeless (fresh-session) attach stays silent so the bootstrap's own |
| 13 | // ready meta is the sidebar's first update — lives in |
| 14 | // TestBootstrapNewSessionPublishesTabUpdateForSidebarBlankRow. |
| 15 | func TestRoutedAttachAnnouncesIdentityWhileConnecting(t *testing.T) { |
| 16 | const sessionPath = "/remote/sessions/s1.jsonl" |
| 17 | fs := newFakeServe(t, "s3cret", []serveSessionEntry{ |
| 18 | {Name: "s1", Path: sessionPath, Title: "First chat", Turns: 2, Current: true}, |
| 19 | }) |
| 20 | kernel := &fakeRemoteKernel{ |
| 21 | statuses: []RemoteConnectionStatusView{{HostID: "box", State: "connected"}}, |
| 22 | ensureView: RemoteServerView{HostID: "box", State: "ready", LocalURL: fs.server.URL}, |
| 23 | ensureToken: "s3cret", |
| 24 | } |
| 25 | seedBridgeTestHost(t, "box") |
| 26 | log := &eventLog{} |
| 27 | updates := make(chan TabMeta, 4) |
| 28 | a := &App{remoteRuntime: kernel, remoteEventHook: func(name string, payload any) { |
| 29 | log.add(name, payload) |
| 30 | if name != "remote-tab:updated" { |
| 31 | return |
| 32 | } |
| 33 | if meta, ok := payload.(TabMeta); ok { |
| 34 | select { |
| 35 | case updates <- meta: |
| 36 | default: |
| 37 | } |
| 38 | } |
| 39 | }} |
| 40 | cleanupRemoteTabPumps(t, a) |
| 41 | meta, err := a.OpenRemoteProjectTab("box", "~/app", RemoteTabOpenOptions{SessionPath: sessionPath}) |
| 42 | if err != nil { |
| 43 | t.Fatal(err) |
| 44 | } |
| 45 | timer := time.NewTimer(3 * time.Second) |
| 46 | defer timer.Stop() |
| 47 | var update TabMeta |
| 48 | for update.ID != meta.ID { |
| 49 | select { |
| 50 | case update = <-updates: |
| 51 | case <-timer.C: |
| 52 | t.Fatalf("routed attach emitted no remote-tab:updated for tab %q, events: %v", meta.ID, log.recorded()) |
| 53 | } |
| 54 | } |
| 55 | if update.SessionPath != sessionPath { |
| 56 | t.Fatalf("attach announcement session = %q, want the routed %q", update.SessionPath, sessionPath) |
| 57 | } |
| 58 | if update.RemoteState != "connecting" { |
| 59 | t.Fatalf("attach announcement state = %q, want it before the ready rotation", update.RemoteState) |
| 60 | } |
| 61 | waitForTabState(t, a, meta.ID, "ready") |
| 62 | } |
| 63 |