返回 DeepSeek-Reasonix
remote_session_organization_test.go
根目录 / desktop / remote_session_organization_test.go
1 package main
2
3 import (
4 "bytes"
5 "encoding/json"
6 "os"
7 "reflect"
8 "strings"
9 "testing"
10
11 "reasonix/internal/config"
12 )
13
14 func TestRemoteSessionOrganizationReadDoesNotStartServeOrRewriteConfig(t *testing.T) {
15 app, kernel := remoteOrganizationFixture(t)
16 before, err := os.ReadFile(config.UserConfigPath())
17 if err != nil {
18 t.Fatal(err)
19 }
20 for _, host := range []string{"host-a", "host-b"} {
21 got, err := app.GetSessionOrganization(SessionOrganizationWorkspace{HostID: host, Scope: "project", WorkspaceRoot: "/same/workspace"})
22 if err != nil {
23 t.Fatal(err)
24 }
25 want := "ref\x00" + host + "\x00same-session"
26 if len(got.Groups) != 1 || !reflect.DeepEqual(got.Groups[0].SessionKeys, []string{want}) {
27 t.Fatalf("%s organization=%#v", host, got)
28 }
29 }
30 after, err := os.ReadFile(config.UserConfigPath())
31 if err != nil {
32 t.Fatal(err)
33 }
34 if !bytes.Equal(before, after) {
35 t.Fatal("read-only remote organization rewrote configuration")
36 }
37 if kernel.ensureCalls != 0 {
38 t.Fatalf("reading remote organization started Serve %d times", kernel.ensureCalls)
39 }
40 }
41
42 func TestRemoteSessionOrganizationCASIsolatesHostsWithSameWorkspaceAndSessionID(t *testing.T) {
43 app, kernel := remoteOrganizationFixture(t)
44 a := SessionOrganizationWorkspace{HostID: "host-a", Scope: "project", WorkspaceRoot: "/same/workspace"}
45 b := SessionOrganizationWorkspace{HostID: "host-b", Scope: "project", WorkspaceRoot: "/same/workspace"}
46 beforeA, err := app.GetSessionOrganization(a)
47 if err != nil {
48 t.Fatal(err)
49 }
50 beforeB, err := app.GetSessionOrganization(b)
51 if err != nil {
52 t.Fatal(err)
53 }
54 winner, err := app.UpdateSessionOrganization(a, beforeA.Revision, SessionOrganizationMutation{Kind: "rename-group", GroupID: "work", Title: "Only host A"})
55 if err != nil || !winner.Applied {
56 t.Fatalf("rename=%#v err=%v", winner, err)
57 }
58 otherWindow := appWithFakeKernel(&fakeRemoteKernel{})
59 stale, err := otherWindow.UpdateSessionOrganization(a, beforeA.Revision, SessionOrganizationMutation{Kind: "rename-group", GroupID: "work", Title: "Stale"})
60 if err != nil || stale.Applied || stale.Revision != winner.Revision || stale.Groups[0].Title != "Only host A" {
61 t.Fatalf("stale CAS=%#v err=%v", stale, err)
62 }
63 afterB, err := otherWindow.GetSessionOrganization(b)
64 if err != nil {
65 t.Fatal(err)
66 }
67 if !reflect.DeepEqual(afterB, beforeB) {
68 t.Fatalf("host A mutation changed host B: before=%#v after=%#v", beforeB, afterB)
69 }
70 if kernel.ensureCalls != 0 {
71 t.Fatal("local remote-organization metadata mutation started Serve")
72 }
73 }
74
75 func TestRemoteSessionOrganizationPreservesUnknownFieldsOnConfigRoundTrip(t *testing.T) {
76 app, _ := remoteOrganizationFixture(t)
77 path := config.UserConfigPath()
78 body, err := os.ReadFile(path)
79 if err != nil {
80 t.Fatal(err)
81 }
82 body = append(body, []byte("\n[future_organization_settings]\nkeep = \"retained\"\n")...)
83 body = bytes.ReplaceAll(body, []byte("[[remote.projects]]"), []byte("[[remote.projects]]\nfuture_project = \"retained\""))
84 if err := os.WriteFile(path, body, 0600); err != nil {
85 t.Fatal(err)
86 }
87 w := SessionOrganizationWorkspace{HostID: "host-a", Scope: "project", WorkspaceRoot: "/same/workspace"}
88 before, err := app.GetSessionOrganization(w)
89 if err != nil {
90 t.Fatal(err)
91 }
92 if _, err := app.UpdateSessionOrganization(w, before.Revision, SessionOrganizationMutation{Kind: "rename-group", GroupID: "work", Title: "Changed"}); err != nil {
93 t.Fatal(err)
94 }
95 cfg, err := config.Load()
96 if err != nil {
97 t.Fatal(err)
98 }
99 for _, p := range cfg.Remote.Projects {
100 var organization map[string]any
101 if err := json.Unmarshal([]byte(p.SessionOrganization), &organization); err != nil {
102 t.Fatal(err)
103 }
104 if !reflect.DeepEqual(organization["futureOrganization"], map[string]any{"enabled": true}) {
105 t.Fatalf("unknown organization field lost for %s", p.HostID)
106 }
107 group := organization["groups"].([]any)[0].(map[string]any)
108 if group["futureGroup"] != "retained" {
109 t.Fatalf("unknown group field lost for %s", p.HostID)
110 }
111 }
112 after, err := os.ReadFile(path)
113 if err != nil {
114 t.Fatal(err)
115 }
116 if !strings.Contains(string(after), "[future_organization_settings]") || !strings.Contains(string(after), `keep = "retained"`) {
117 t.Fatal("remote organization save removed unknown TOML settings")
118 }
119 if strings.Count(string(after), `future_project = 'retained'`)+strings.Count(string(after), `future_project = "retained"`) != 2 {
120 t.Fatal("remote organization save removed unknown per-project TOML settings")
121 }
122 }
123
124 func remoteOrganizationFixture(t *testing.T) (*App, *fakeRemoteKernel) {
125 t.Helper()
126 isolateDesktopUserDirs(t)
127 if err := editUserConfig(func(c *config.Config) error {
128 for _, host := range []string{"host-a", "host-b"} {
129 if err := c.UpsertRemoteHost(config.RemoteHostEntry{Name: host, Host: "127.0.0.1", Port: 22, User: "test"}); err != nil {
130 return err
131 }
132 key := "ref\x00" + host + "\x00same-session"
133 organization, err := json.Marshal(map[string]any{
134 "revision": 7, "manualOrderEnabled": true, "order": []string{key}, "migrationVersion": 1, "imported": map[string]bool{key: true},
135 "groups": []map[string]any{{"id": "work", "title": "Work", "members": []string{key}, "futureGroup": "retained"}},
136 "futureOrganization": map[string]bool{"enabled": true},
137 })
138 if err != nil {
139 return err
140 }
141 c.Remote.Projects = append(c.Remote.Projects, config.RemoteProjectEntry{HostID: host, Workspace: "/same/workspace", Title: host, SessionOrganization: string(organization)})
142 }
143 return nil
144 }); err != nil {
145 t.Fatal(err)
146 }
147 kernel := &fakeRemoteKernel{}
148 return appWithFakeKernel(kernel), kernel
149 }
150
150 lines GO