| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "errors" |
| 5 | "testing" |
| 6 | |
| 7 | "reasonix/internal/boot" |
| 8 | "reasonix/internal/control" |
| 9 | "reasonix/internal/event" |
| 10 | "reasonix/internal/session" |
| 11 | ) |
| 12 | |
| 13 | func TestCanonicalNavigationLastRequestWins(t *testing.T) { |
| 14 | app, tab, target, _, _ := canonicalWorkspaceOpenFixture(t) |
| 15 | ctrl := tab.Ctrl |
| 16 | tab.Ctrl = &activeNavigationController{SessionAPI: ctrl, IdentityLifecycle: ctrl.(control.IdentityLifecycle), status: control.RuntimeStatus{Running: true}} |
| 17 | source := session.SessionRef{HostID: localDesktopHostID, SessionID: tab.SessionID} |
| 18 | app.runtimeRebuildMu.Lock() |
| 19 | locked := true |
| 20 | defer func() { |
| 21 | if locked { |
| 22 | app.runtimeRebuildMu.Unlock() |
| 23 | } |
| 24 | }() |
| 25 | first, second := make(chan error, 1), make(chan error, 1) |
| 26 | go func() { _, err := app.OpenSession(target.Ref()); first <- err }() |
| 27 | waitFor(t, "first navigation admission", func() bool { return app.desktopSessions.navigationSeq.Load() == 1 }) |
| 28 | go func() { _, err := app.OpenSession(source); second <- err }() |
| 29 | waitFor(t, "second navigation admission", func() bool { return app.desktopSessions.navigationSeq.Load() == 2 }) |
| 30 | app.runtimeRebuildMu.Unlock() |
| 31 | locked = false |
| 32 | if err := <-first; !errors.Is(err, errSessionNavigationSuperseded) { |
| 33 | t.Fatalf("first open = %v", err) |
| 34 | } |
| 35 | if err := <-second; err != nil { |
| 36 | t.Fatalf("last open = %v", err) |
| 37 | } |
| 38 | if tab.SessionID != source.SessionID { |
| 39 | t.Fatal("superseded open won") |
| 40 | } |
| 41 | } |
| 42 | |
| 43 | func TestTicketedCanonicalNavigationKeepsOriginalIntent(t *testing.T) { |
| 44 | app, _, target, _, _ := canonicalWorkspaceOpenFixture(t) |
| 45 | if _, err := app.StartTopicActivation(TopicActivationRequest{SessionPath: sessionRoute(target.Ref().SessionID)}); err != nil { |
| 46 | t.Fatal(err) |
| 47 | } |
| 48 | if got := app.desktopSessions.navigationSeq.Load(); got != 1 { |
| 49 | t.Fatalf("one navigation claimed %d intents; a nested open can overtake a newer queued request", got) |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | func TestDelayedCanonicalNavigationCannotReclaimSupersededIntent(t *testing.T) { |
| 54 | app, tab, target, _, _ := canonicalWorkspaceOpenFixture(t) |
| 55 | intent := app.desktopSessions.navigationSeq.Add(1) |
| 56 | if err := app.SetActiveTab(tab.ID); err != nil { |
| 57 | t.Fatal(err) |
| 58 | } |
| 59 | before := tab.SessionID |
| 60 | if _, err := app.openTopicSessionWithNavigation("project", tab.WorkspaceRoot, "", sessionRoute(target.Ref().SessionID), intent); !errors.Is(err, errSessionNavigationSuperseded) { |
| 61 | t.Fatalf("late adopted source open = %v", err) |
| 62 | } |
| 63 | if tab.SessionID != before { |
| 64 | t.Fatal("late source adoption displaced the newer selection") |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | func TestCanonicalOpenReattachesDetachedWorkspace(t *testing.T) { |
| 69 | app, tab, target, root, workspaceID := canonicalWorkspaceOpenFixture(t) |
| 70 | ctrl, err := app.buildTabControllerBoot(app.ctx, boot.Options{Model: tab.model, WorkspaceRoot: root, SessionDir: desktopSessionDir(root), Sink: event.Discard}) |
| 71 | if err != nil { |
| 72 | t.Fatal(err) |
| 73 | } |
| 74 | t.Cleanup(ctrl.Close) |
| 75 | if _, err := ctrl.(control.IdentityLifecycle).OpenSession(t.Context(), target.Ref()); err != nil { |
| 76 | t.Fatal(err) |
| 77 | } |
| 78 | detached := &WorkspaceTab{ID: "detached-B", Scope: "project", WorkspaceRoot: root, SessionID: target.Ref().SessionID, Ready: true, Ctrl: ctrl, model: tab.model, sink: &tabEventSink{tabID: "detached-B", app: app}} |
| 79 | detached.SessionWorkspace.ID = workspaceID |
| 80 | key := sessionRuntimeKey(sessionRoute(target.Ref().SessionID)) |
| 81 | app.ensureDetachedSessionsLocked() |
| 82 | app.detachedSessions[key] = detached |
| 83 | app.newSessionRuntimeLocked(detached, key) |
| 84 | if _, err := app.OpenSession(target.Ref()); err != nil { |
| 85 | t.Fatal(err) |
| 86 | } |
| 87 | if tab.Ctrl != ctrl || !sameDesktopPath(tab.WorkspaceRoot, root) || app.detachedSessions[key] != nil { |
| 88 | t.Fatal("did not transfer the exact detached owner") |
| 89 | } |
| 90 | if runtime := app.runtimeBySessionKey[key]; runtime == nil || runtime.Owner != tab { |
| 91 | t.Fatal("runtime map has a stale owner") |
| 92 | } |
| 93 | } |
| 94 | |
| 95 | func TestCanonicalLegacyResumeUsesTargetWorkspace(t *testing.T) { |
| 96 | for _, page := range []bool{false, true} { |
| 97 | t.Run(map[bool]string{false: "messages", true: "page"}[page], func(t *testing.T) { |
| 98 | app, tab, target, root, _ := canonicalWorkspaceOpenFixture(t) |
| 99 | tab.HistoricalSource = &SessionSourceRef{Path: "/fixture/old.jsonl"} |
| 100 | var err error |
| 101 | if page { |
| 102 | _, err = app.ResumeSessionPageForTab(tab.ID, sessionRoute(target.Ref().SessionID), 32) |
| 103 | } else { |
| 104 | _, err = app.ResumeSessionForTab(tab.ID, sessionRoute(target.Ref().SessionID)) |
| 105 | } |
| 106 | if err != nil { |
| 107 | t.Fatal(err) |
| 108 | } |
| 109 | if !sameDesktopPath(tab.WorkspaceRoot, root) { |
| 110 | t.Fatal("compatibility entry kept source workspace") |
| 111 | } |
| 112 | if tab.HistoricalSource != nil { |
| 113 | t.Fatal("compatibility entry retained the preparation action") |
| 114 | } |
| 115 | }) |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | func TestCanonicalOpenGlobalWorkspace(t *testing.T) { |
| 120 | app, tab, _, _, _ := canonicalWorkspaceOpenFixture(t) |
| 121 | workspaceID, err := app.ensureDesktopWorkspace(t.Context(), "global", "") |
| 122 | if err != nil { |
| 123 | t.Fatal(err) |
| 124 | } |
| 125 | target, err := app.desktopSessionService("").Create(t.Context(), session.CreateOptions{SessionID: "global-target", CWD: globalWorkspaceRoot(), Origin: session.SessionOriginNew}) |
| 126 | if err != nil { |
| 127 | t.Fatal(err) |
| 128 | } |
| 129 | if err := app.workspaceRegistry().AttachSession(t.Context(), "", workspaceID, target.Ref().SessionID, ""); err != nil { |
| 130 | t.Fatal(err) |
| 131 | } |
| 132 | if _, err := app.OpenSession(target.Ref()); err != nil { |
| 133 | t.Fatal(err) |
| 134 | } |
| 135 | if tab.Scope != "global" || tab.SessionWorkspace.ID != workspaceID || !sameDesktopPath(tab.WorkspaceRoot, globalWorkspaceRoot()) { |
| 136 | t.Fatal("global target inherited project context") |
| 137 | } |
| 138 | } |
| 139 |