Repository navigation
Expand file tree
/
Copy pathwebdav_cli_test.go
More file actions
228 lines (210 loc) · 6.6 KB
/
Copy pathwebdav_cli_test.go
File metadata and controls
228 lines (210 loc) · 6.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
package main
import (
"bufio"
"io"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"slices"
"strings"
"sync"
"testing"
"github.com/yetone/magpie/internal/agentenv"
"github.com/yetone/magpie/internal/davsync"
"github.com/yetone/magpie/internal/provider"
)
// webdavHome is a machine of its own: no agent on it, no gateway at the
// address, and nothing on stdin.
func webdavHome(t *testing.T) {
t.Helper()
home := t.TempDir()
t.Setenv("HOME", home)
t.Setenv("USERPROFILE", home)
t.Setenv("XDG_CONFIG_HOME", filepath.Join(home, ".config"))
t.Setenv("XDG_CACHE_HOME", filepath.Join(home, ".cache"))
t.Setenv("PATH", "")
for _, v := range agentenv.Vars {
t.Setenv(v, "")
}
t.Setenv("APPDATA", "")
t.Setenv("LOCALAPPDATA", "")
t.Setenv("CLAUDE_CONFIG_DIR", filepath.Join(home, ".claude"))
t.Setenv("CODEX_HOME", filepath.Join(home, ".codex"))
t.Setenv("MAGPIE_ADDR", "127.0.0.1:1")
piped(t)
}
// davServer is a WebDAV server for user me with a password of its own,
// keeping files in memory, and every password it was sent.
type davServer struct {
mu sync.Mutex
pass string
files map[string][]byte
sent []string
url string
}
func newDAVServer(t *testing.T, pass string) *davServer {
t.Helper()
d := &davServer{pass: pass, files: map[string][]byte{}}
srv := httptest.NewServer(d)
t.Cleanup(srv.Close)
d.url = srv.URL + "/dav/"
return d
}
func (d *davServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
d.mu.Lock()
defer d.mu.Unlock()
u, p, ok := r.BasicAuth()
if ok {
d.sent = append(d.sent, p)
}
if !ok || u != "me" || p != d.pass {
w.WriteHeader(http.StatusUnauthorized)
return
}
switch r.Method {
case http.MethodGet:
b, ok := d.files[r.URL.Path]
if !ok {
w.WriteHeader(http.StatusNotFound)
return
}
w.Write(b)
case http.MethodPut:
d.files[r.URL.Path], _ = io.ReadAll(r.Body)
w.WriteHeader(http.StatusCreated)
case "MKCOL":
w.WriteHeader(http.StatusCreated)
default:
w.WriteHeader(http.StatusMethodNotAllowed)
}
}
func (d *davServer) file() []byte {
d.mu.Lock()
defer d.mu.Unlock()
return d.files["/dav/magpie/magpie.magpie-backup"]
}
func (d *davServer) passwords() []string {
d.mu.Lock()
defer d.mu.Unlock()
return slices.Clone(d.sent)
}
// piped is what stdin holds for the secrets asked, a line each: a pipe, so
// they are read as a script's are, never from the terminal the test is in.
func piped(t *testing.T, lines ...string) {
t.Helper()
r, w, err := os.Pipe()
if err != nil {
t.Fatal(err)
}
if len(lines) > 0 {
w.WriteString(strings.Join(lines, "\n") + "\n")
}
w.Close()
oldFile, oldReader := os.Stdin, stdin
os.Stdin, stdin = r, bufio.NewReader(r)
t.Cleanup(func() {
os.Stdin, stdin = oldFile, oldReader
r.Close()
})
}
func TestWebdavCmd(t *testing.T) {
webdavHome(t)
first := newDAVServer(t, "pw")
provider.Save(provider.Provider{ID: "deepseek", Name: "DeepSeek", Chat: "https://api.deepseek.com/v1", Key: "k1"})
// off: nothing to change or sync
if err := webdavCmd([]string{"set", "agents=no"}); err == nil || !strings.Contains(err.Error(), "is off") {
t.Fatalf("set while off: %v", err)
}
if err := webdavCmd([]string{"now"}); err == nil || !strings.Contains(err.Error(), "is off") {
t.Fatalf("now while off: %v", err)
}
if err := webdavCmd(nil); err != nil {
t.Fatal(err)
}
// a wrong address is said before any secret is asked for
if err := webdavCmd([]string{"on", "dav.example.com", "user=me"}); err == nil || !strings.Contains(err.Error(), "not a WebDAV address") {
t.Fatalf("an address with no https://: %v", err)
}
// on: the password and the passphrase asked for, and a sync at once
piped(t, "pw", "correct horse")
if err := webdavCmd([]string{"on", first.url, "user=me", "library=no"}); err != nil {
t.Fatal(err)
}
c, ok := davsync.Load()
if !ok || c.URL != first.url || c.User != "me" || c.Password != "pw" || c.Passphrase != "correct horse" ||
!c.Keys || !c.Agents || c.Library == nil || *c.Library {
t.Fatalf("on: %v %+v", ok, c)
}
if up := first.file(); len(up) == 0 || strings.Contains(string(up), "deepseek") {
t.Fatalf("on the server: %q", up)
}
if v := davsync.Status(); v.Error != "" || v.Last.IsZero() {
t.Fatalf("status: %+v", v)
}
// set: what is given changes, the rest (secrets too) stays
if err := webdavCmd([]string{"set", "agents=no", "keys=no"}); err != nil {
t.Fatal(err)
}
if c, _ := davsync.Load(); c.Agents || c.Keys || c.Password != "pw" || c.Passphrase != "correct horse" || c.URL != first.url {
t.Fatalf("set: %+v", c)
}
// an address with = in it, bare: the same server, so the password stays
if err := webdavCmd([]string{"set", first.url + "?k=v"}); err != nil {
t.Fatal(err)
}
if c, _ := davsync.Load(); c.URL != first.url+"?k=v" || c.Password != "pw" {
t.Fatalf("an address with = in it: %+v", c)
}
for _, bad := range [][]string{{"set", "passphrase=on-the-command-line"}, {"set", "password=on-the-command-line"}, {"set", "keys=maybe"}, {"set", "colour=red"}, {"set", first.url, first.url}} {
if err := webdavCmd(bad); err == nil {
t.Errorf("%v: no error", bad)
}
}
// password= asks for a new one; a wrong one shows at once, and it stays
// on to be put right
piped(t, "nope")
if err := webdavCmd([]string{"set", "password="}); err == nil || !strings.Contains(err.Error(), "401") {
t.Fatalf("wrong password: %v", err)
}
if c, ok := davsync.Load(); !ok || c.Password != "nope" {
t.Fatalf("after a failed sync: %v %+v", ok, c)
}
piped(t, "pw")
if err := webdavCmd([]string{"set", "password="}); err != nil {
t.Fatal(err)
}
// another server: the password saved is not sent there, and one is
// asked for
second := newDAVServer(t, "pw2")
piped(t, "pw2")
if err := webdavCmd([]string{"set", second.url}); err != nil {
t.Fatal(err)
}
if slices.Contains(second.passwords(), "pw") {
t.Fatal("the first server's password went to the second")
}
if c, _ := davsync.Load(); c.URL != second.url || c.Password != "pw2" || c.Passphrase != "correct horse" {
t.Fatalf("moved: %+v", c)
}
// user= alone: no sign-in, nothing asked for, no password sent
sent := len(second.passwords())
if err := webdavCmd([]string{"set", "user="}); err == nil || !strings.Contains(err.Error(), "401") {
t.Fatalf("no sign-in, on a server that asks for one: %v", err)
}
if c, _ := davsync.Load(); c.User != "" || c.Password != "" {
t.Fatalf("user= alone: %+v", c)
}
if n := len(second.passwords()); n != sent {
t.Fatalf("%d passwords sent with no user", n-sent)
}
if err := webdavCmd([]string{"off"}); err != nil {
t.Fatal(err)
}
if _, ok := davsync.Load(); ok {
t.Fatal("still on after off")
}
if len(second.file()) == 0 {
t.Fatal("off took the file off the server")
}
}