refactor: flatten repository structure to root and archive research notes

This commit is contained in:
OpenClaw Agent
2026-09-22 03:41:27 +08:00
parent ae0becffec
commit 1a7658b1f7
40 changed files with 0 additions and 3 deletions
+194
View File
@@ -0,0 +1,194 @@
package api
import (
"bytes"
"io"
"mime/multipart"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"strings"
"testing"
"chfs-ng/internal/auth"
"chfs-ng/internal/storage"
)
func permissionTestServer(t *testing.T, mask auth.PermMask) (*Server, string) {
t.Helper()
root := t.TempDir()
if err := os.MkdirAll(filepath.Join(root, "dir"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(root, "file.txt"), []byte("hello"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(root, "dir", "nested.txt"), []byte("nested"), 0o644); err != nil {
t.Fatal(err)
}
s := &Server{
Store: storage.Local{Root: root},
Auth: &auth.Manager{
Accounts: map[string]*auth.Account{},
Guest: &auth.Account{Mask: mask},
},
RemoveMode: 3,
ImagePreview: true,
}
return s, root
}
func doRequest(h http.Handler, method, target string, body io.Reader, contentType string) *httptest.ResponseRecorder {
req := httptest.NewRequest(method, target, body)
if contentType != "" {
req.Header.Set("Content-Type", contentType)
}
rr := httptest.NewRecorder()
h.ServeHTTP(rr, req)
return rr
}
func multipartField(t *testing.T, field, value string) (*bytes.Buffer, string) {
t.Helper()
var b bytes.Buffer
mw := multipart.NewWriter(&b)
if err := mw.WriteField(field, value); err != nil {
t.Fatal(err)
}
if err := mw.Close(); err != nil {
t.Fatal(err)
}
return &b, mw.FormDataContentType()
}
func multipartUpload(t *testing.T, name, data string) (*bytes.Buffer, string) {
t.Helper()
var b bytes.Buffer
mw := multipart.NewWriter(&b)
fw, err := mw.CreateFormFile("file", name)
if err != nil {
t.Fatal(err)
}
if _, err := io.WriteString(fw, data); err != nil {
t.Fatal(err)
}
if err := mw.WriteField("folder", ""); err != nil {
t.Fatal(err)
}
if err := mw.Close(); err != nil {
t.Fatal(err)
}
return &b, mw.FormDataContentType()
}
func TestHTTPPermissionMatrix(t *testing.T) {
cases := []struct {
name string
mask auth.PermMask
read int
write int
del int
}{
{"none", auth.None, http.StatusForbidden, http.StatusForbidden, http.StatusForbidden},
{"read", auth.Read, http.StatusOK, http.StatusForbidden, http.StatusForbidden},
{"read-write", auth.Read | auth.Write, http.StatusOK, http.StatusCreated, http.StatusForbidden},
{"read-write-delete", auth.Read | auth.Write | auth.Delete, http.StatusOK, http.StatusCreated, http.StatusNoContent},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
s, root := permissionTestServer(t, tc.mask)
h := s.Handler()
files := doRequest(h, http.MethodGet, "/chfs/files?filepath=", nil, "")
if files.Code != http.StatusOK {
t.Fatalf("files: got %d", files.Code)
}
body, ct := multipartUpload(t, "uploaded.txt", "upload")
upload := doRequest(h, http.MethodPost, "/chfs/upload", body, ct)
if upload.Code != tc.write {
t.Errorf("upload: got %d want %d", upload.Code, tc.write)
}
_, _ = os.Stat(filepath.Join(root, "uploaded.txt"))
body, ct = multipartField(t, "filepath", "newdir")
newdir := doRequest(h, http.MethodPost, "/chfs/newdir", body, ct)
if newdir.Code != tc.write {
t.Errorf("newdir: got %d want %d", newdir.Code, tc.write)
}
body, ct = multipartField(t, "old", "file.txt")
body2, _ := multipartField(t, "new", "renamed.txt")
var renameBody bytes.Buffer
renameBody.Write(body.Bytes())
renameBody.Write(body2.Bytes())
// Rebuild the combined multipart request because each helper owns its boundary.
var rb bytes.Buffer
mw := multipart.NewWriter(&rb)
_ = mw.WriteField("old", "file.txt")
_ = mw.WriteField("new", "renamed.txt")
_ = mw.Close()
_ = body
_ = ct
rename := doRequest(h, http.MethodPost, "/chfs/rename", &rb, mw.FormDataContentType())
if rename.Code != tc.write {
t.Errorf("rename: got %d want %d", rename.Code, tc.write)
}
deleteBody, deleteCT := multipartField(t, "filepath", "dir/nested.txt")
del := doRequest(h, http.MethodDelete, "/chfs/rmfiles", deleteBody, deleteCT)
if del.Code != tc.del {
t.Errorf("delete: got %d want %d", del.Code, tc.del)
}
zip := doRequest(h, http.MethodGet, "/chfs/downloaddir/dir", nil, "")
if zip.Code != tc.read {
t.Errorf("zip: got %d want %d", zip.Code, tc.read)
}
})
}
}
func TestHTTPBatchDeleteAndTransferConflict(t *testing.T) {
s, root := permissionTestServer(t, auth.Read|auth.Write|auth.Delete)
h := s.Handler()
if err := os.WriteFile(filepath.Join(root, "second.txt"), []byte("second"), 0o644); err != nil {
t.Fatal(err)
}
var b bytes.Buffer
mw := multipart.NewWriter(&b)
_ = mw.WriteField("filepath", "file.txt")
_ = mw.WriteField("filepath", "second.txt")
_ = mw.Close()
rr := doRequest(h, http.MethodDelete, "/chfs/rmfiles", &b, mw.FormDataContentType())
if rr.Code != http.StatusNoContent {
t.Fatalf("batch delete: got %d", rr.Code)
}
if _, err := os.Stat(filepath.Join(root, "file.txt")); !os.IsNotExist(err) {
t.Fatalf("file.txt still exists: %v", err)
}
if _, err := os.Stat(filepath.Join(root, "second.txt")); !os.IsNotExist(err) {
t.Fatalf("second.txt still exists: %v", err)
}
if err := os.WriteFile(filepath.Join(root, "source.txt"), []byte("source"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(root, "target.txt"), []byte("target"), 0o644); err != nil {
t.Fatal(err)
}
form := url.Values{"source": {"source.txt"}, "destination": {"target.txt"}}
copyReq := doRequest(h, http.MethodPost, "/chfs/copy", strings.NewReader(form.Encode()), "application/x-www-form-urlencoded")
if copyReq.Code != http.StatusBadRequest {
t.Fatalf("copy conflict: got %d", copyReq.Code)
}
data, err := os.ReadFile(filepath.Join(root, "target.txt"))
if err != nil {
t.Fatal(err)
}
if string(data) != "target" {
t.Fatalf("target overwritten: %q", data)
}
}