package server
import (
"net/http"
"os"
"path/filepath"
"strings"
"testing"
)
func TestDeadPropsPersistence(t *testing.T) {
s, store, dir := newTestServer(t)
_, pass, _ := store.Create("app1", false, "")
propDir := filepath.Join(dir, "davprops", "app1")
// 先放一个文件。
rec := request(s, http.MethodPut, "/app1/a.txt", "app1", pass, strings.NewReader("x"), nil)
if rec.Code != http.StatusCreated {
t.Fatalf("PUT 应为 201,实际 %d", rec.Code)
}
setBody := `` +
`` +
`alice` +
``
rec = request(s, "PROPPATCH", "/app1/a.txt", "app1", pass, strings.NewReader(setBody), map[string]string{"Content-Type": "application/xml"})
if rec.Code != 207 {
t.Fatalf("PROPPATCH 应为 207,实际 %d", rec.Code)
}
if !strings.Contains(rec.Body.String(), "HTTP/1.1 200 OK") {
t.Fatalf("PROPPATCH 应返回 200 propstat,实际 %s", rec.Body.String())
}
if _, err := os.Stat(filepath.Join(propDir, "a.txt", "__props__.json")); err != nil {
t.Fatalf("应写出 sidecar: %v", err)
}
// sidecar 不在账号 root 内。
if _, err := os.Stat(filepath.Join(dir, "data", "app1", "davprops")); !os.IsNotExist(err) {
t.Fatal("账号 root 内不应出现 davprops 目录")
}
findBody := `` +
``
rec = request(s, "PROPFIND", "/app1/a.txt", "app1", pass, strings.NewReader(findBody), map[string]string{"Depth": "0"})
if rec.Code != 207 || !strings.Contains(rec.Body.String(), "alice") {
t.Fatalf("PROPFIND 应读回死属性,实际 %d %s", rec.Code, rec.Body.String())
}
// COPY 跟随死属性。
rec = request(s, "COPY", "/app1/a.txt", "app1", pass, nil, map[string]string{
"Destination": "http://example.com/app1/c.txt",
})
if rec.Code != http.StatusCreated {
t.Fatalf("COPY 应为 201,实际 %d", rec.Code)
}
rec = request(s, "PROPFIND", "/app1/c.txt", "app1", pass, strings.NewReader(findBody), map[string]string{"Depth": "0"})
if rec.Code != 207 || !strings.Contains(rec.Body.String(), "alice") {
t.Fatalf("COPY 后死属性应跟随,实际 %d %s", rec.Code, rec.Body.String())
}
// MOVE 跟随死属性。
rec = request(s, "MOVE", "/app1/a.txt", "app1", pass, nil, map[string]string{
"Destination": "http://example.com/app1/m.txt",
})
if rec.Code != http.StatusCreated && rec.Code != http.StatusNoContent {
t.Fatalf("MOVE 应为 201/204,实际 %d", rec.Code)
}
rec = request(s, "PROPFIND", "/app1/m.txt", "app1", pass, strings.NewReader(findBody), map[string]string{"Depth": "0"})
if rec.Code != 207 || !strings.Contains(rec.Body.String(), "alice") {
t.Fatalf("MOVE 后死属性应跟随,实际 %d %s", rec.Code, rec.Body.String())
}
if _, err := os.Stat(filepath.Join(propDir, "a.txt")); !os.IsNotExist(err) {
t.Fatal("MOVE 后旧 sidecar 应消失")
}
// 目录列表(PROPFIND Depth:1)不得出现 sidecar。
rec = request(s, "PROPFIND", "/app1/", "app1", pass, nil, map[string]string{"Depth": "1"})
if rec.Code != 207 {
t.Fatalf("PROPFIND Depth:1 应为 207,实际 %d", rec.Code)
}
if strings.Contains(rec.Body.String(), "davprops") || strings.Contains(rec.Body.String(), "__props__") {
t.Fatalf("目录列表不应暴露 sidecar: %s", rec.Body.String())
}
// PROPPATCH remove 后读不到,sidecar 清理。
removeBody := `` +
`` +
`` +
``
rec = request(s, "PROPPATCH", "/app1/c.txt", "app1", pass, strings.NewReader(removeBody), map[string]string{"Content-Type": "application/xml"})
if rec.Code != 207 {
t.Fatalf("PROPPATCH remove 应为 207,实际 %d", rec.Code)
}
rec = request(s, "PROPFIND", "/app1/c.txt", "app1", pass, strings.NewReader(findBody), map[string]string{"Depth": "0"})
if strings.Contains(rec.Body.String(), "alice") {
t.Fatalf("remove 后不应再读到死属性: %s", rec.Body.String())
}
if _, err := os.Stat(filepath.Join(propDir, "c.txt")); !os.IsNotExist(err) {
t.Fatal("remove 后 sidecar 应清理")
}
// DELETE 清理死属性。
rec = request(s, http.MethodDelete, "/app1/m.txt", "app1", pass, nil, nil)
if rec.Code != http.StatusNoContent {
t.Fatalf("DELETE 应为 204,实际 %d", rec.Code)
}
if _, err := os.Stat(filepath.Join(propDir, "m.txt")); !os.IsNotExist(err) {
t.Fatal("DELETE 后 sidecar 应清理")
}
}