| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "encoding/json" |
| 5 | "path/filepath" |
| 6 | "reasonix/desktop/internal/draftstate" |
| 7 | "reasonix/internal/config" |
| 8 | "reasonix/internal/control" |
| 9 | "testing" |
| 10 | ) |
| 11 | |
| 12 | func TestDraftRetryRebuildsCanonicalControllerAndPreservesFailedCandidate(t *testing.T) { |
| 13 | a, tab, old, _ := auditMigratedTab(t) |
| 14 | cfg, err := config.LoadForRootReadOnly(tab.WorkspaceRoot) |
| 15 | if err != nil { |
| 16 | t.Fatal(err) |
| 17 | } |
| 18 | for i := range cfg.Providers { |
| 19 | if cfg.Providers[i].Name == "new" { |
| 20 | cfg.Providers[i].SupportedEfforts = []string{"low", "high"} |
| 21 | cfg.Providers[i].DefaultEffort = "low" |
| 22 | } |
| 23 | } |
| 24 | if err := cfg.SaveTo(config.UserConfigPath()); err != nil { |
| 25 | t.Fatal(err) |
| 26 | } |
| 27 | a.desktopDrafts = draftstate.New(filepath.Join(t.TempDir(), "drafts.sqlite")) |
| 28 | t.Cleanup(func() { _ = a.desktopDrafts.Close(); tab.Ctrl.Close() }) |
| 29 | _, op := beginDraftTestOperation(t, a, "starting") |
| 30 | // Bind the fixture operation to the already canonical session, as a retry |
| 31 | // does after the first Controller has become ready but admission failed. |
| 32 | op.SessionID = tab.SessionID |
| 33 | identity := tab.SessionID |
| 34 | settings := SessionDraftSettings{Model: "new/new-model", Effort: "high", ToolApprovalMode: "read-only", CollaborationMode: "plan", DisabledMCP: map[string]ServerView{"unused": {Name: "unused"}}, MCPOrder: []string{"unused"}} |
| 35 | if err := a.prepareDraftRuntime(op, settings); err != nil { |
| 36 | t.Fatal(err) |
| 37 | } |
| 38 | if tab.Ctrl == old || tab.SessionID != identity { |
| 39 | t.Fatal("retry did not replace the graph on the same Session") |
| 40 | } |
| 41 | if err := a.verifyDraftRuntime(tab.ID, settings); err != nil { |
| 42 | t.Fatal(err) |
| 43 | } |
| 44 | current := tab.Ctrl |
| 45 | settings.Model = "removed/model" |
| 46 | if err := a.prepareDraftRuntime(op, settings); err == nil { |
| 47 | t.Fatal("missing model accepted") |
| 48 | } |
| 49 | if tab.Ctrl != current || tab.SessionID != identity { |
| 50 | t.Fatal("failed candidate replaced the execution owner") |
| 51 | } |
| 52 | } |
| 53 | |
| 54 | func TestDraftCancelWaitsForWorkerOwnership(t *testing.T) { |
| 55 | isolateDesktopUserDirs(t) |
| 56 | a := newDraftTestApp(t) |
| 57 | _, op := beginDraftTestOperation(t, a, "starting") |
| 58 | release, err := a.draftStore().WorkerLease(op.SessionID) |
| 59 | if err != nil { |
| 60 | t.Fatal(err) |
| 61 | } |
| 62 | defer release() |
| 63 | view, err := a.CancelDraftSubmission(op.ID) |
| 64 | if err != nil { |
| 65 | t.Fatal(err) |
| 66 | } |
| 67 | if view.Phase != "cancel_requested" || view.CanEdit || view.CanResume { |
| 68 | t.Fatalf("cancel unlocked live worker: %+v", view) |
| 69 | } |
| 70 | if unlock, err := a.lockDraftRuntimePublication(op.ID); err == nil { |
| 71 | unlock() |
| 72 | t.Fatal("cancelled worker can publish a runtime") |
| 73 | } |
| 74 | a.finishDraftCancellation(op) |
| 75 | view, err = a.GetDraftSubmission(op.ID) |
| 76 | if err != nil || view.Phase != "cancelled" { |
| 77 | t.Fatalf("worker finish: %+v %v", view, err) |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | func TestDraftV3SnapshotSurvivesV5DatabaseAndChangedEditor(t *testing.T) { |
| 82 | a := newDraftTestApp(t) |
| 83 | draft, op := beginDraftTestOperation(t, a, "reserved") |
| 84 | request := SessionDraftSubmissionRequest{SnapshotVersion: 3, Settings: SessionDraftSettings{Model: "frozen/model", ToolApprovalMode: "read-only"}} |
| 85 | encoded, err := json.Marshal(request) |
| 86 | if err != nil { |
| 87 | t.Fatal(err) |
| 88 | } |
| 89 | op.RequestJSON = string(encoded) |
| 90 | settings, err := a.draftOperationSettings(op) |
| 91 | if err != nil || settings.Model != "frozen/model" { |
| 92 | t.Fatalf("frozen settings: %+v %v (draft %s)", settings, err, draft.ID) |
| 93 | } |
| 94 | request.SnapshotVersion = draftstate.SnapshotVersion + 1 |
| 95 | encoded, _ = json.Marshal(request) |
| 96 | op.RequestJSON = string(encoded) |
| 97 | if _, err = a.draftOperationSettings(op); err == nil { |
| 98 | t.Fatal("future snapshot was interpreted as current editor settings") |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | func TestDraftV5InheritedModelSnapshotRemainsFrozenForRetry(t *testing.T) { |
| 103 | a := newDraftTestApp(t) |
| 104 | _, op := beginDraftTestOperation(t, a, "reserved") |
| 105 | request := SessionDraftSubmissionRequest{ |
| 106 | SnapshotVersion: draftstate.SnapshotVersion, |
| 107 | Settings: SessionDraftSettings{ |
| 108 | Model: "fixture/frozen", ModelSource: draftModelSourceDefault, ToolApprovalMode: "read-only", |
| 109 | }, |
| 110 | } |
| 111 | encoded, err := json.Marshal(request) |
| 112 | if err != nil { |
| 113 | t.Fatal(err) |
| 114 | } |
| 115 | op.RequestJSON = string(encoded) |
| 116 | settings, err := a.draftOperationSettings(op) |
| 117 | if err != nil { |
| 118 | t.Fatal(err) |
| 119 | } |
| 120 | if settings.Model != "fixture/frozen" || settings.ModelSource != draftModelSourceDefault { |
| 121 | t.Fatalf("retry settings = %+v, want the submission-time effective model", settings) |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | func TestDraftRetryChangesActualReadyControllerPermission(t *testing.T) { |
| 126 | isolateDesktopUserDirs(t) |
| 127 | a := newDraftTestApp(t) |
| 128 | _, op := beginDraftTestOperation(t, a, "starting") |
| 129 | ctrl := newFixtureController(t, control.Options{ModelRef: "fixture/model"}) |
| 130 | ctrl.SetToolApprovalMode(control.ToolApprovalDangerFullAccess) |
| 131 | tab := &WorkspaceTab{ID: "tab", SessionID: op.SessionID, Ctrl: ctrl, model: "fixture/model", toolApprovalMode: control.ToolApprovalDangerFullAccess} |
| 132 | a.tabs[tab.ID] = tab |
| 133 | a.tabOrder = []string{tab.ID} |
| 134 | settings := SessionDraftSettings{Model: "fixture/model", ToolApprovalMode: "read-only", CollaborationMode: "plan"} |
| 135 | if err := a.prepareDraftRuntime(op, settings); err != nil { |
| 136 | t.Fatal(err) |
| 137 | } |
| 138 | if ctrl.ToolApprovalMode() != normalizeToolApprovalMode(settings.ToolApprovalMode) || !ctrl.PlanMode() { |
| 139 | t.Fatalf("actual runtime retained permission=%s plan=%v", ctrl.ToolApprovalMode(), ctrl.PlanMode()) |
| 140 | } |
| 141 | if err := a.verifyDraftRuntime(tab.ID, settings); err != nil { |
| 142 | t.Fatal(err) |
| 143 | } |
| 144 | } |
| 145 |