91 lines
3.6 KiB
Go
91 lines
3.6 KiB
Go
package api
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestUploadsReturnSharedRelativeLinks(t *testing.T) {
|
|
f := newFixture(t)
|
|
f.write("notes/a.md", "正文")
|
|
f.write("notes/b.md", "正文")
|
|
var body bytes.Buffer
|
|
form := multipart.NewWriter(&body)
|
|
file, err := form.CreateFormFile("file", "image.png")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := io.WriteString(file, "image"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := form.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var previous string
|
|
for _, query := range []string{"note=notes/a.md", "note=notes/b.md", "folder=notes"} {
|
|
result := decode[struct{ Link string }](t, f.do(f.admin, "POST", "/api/v1/assets?"+query, body.String(), map[string]string{"Content-Type": form.FormDataContentType()}))
|
|
if !strings.HasPrefix(result.Link, "./assets/") || (previous != "" && previous != result.Link) {
|
|
t.Fatalf("上传链接或共享去重不符:%s", result.Link)
|
|
}
|
|
previous = result.Link
|
|
res := f.do(f.admin, "GET", "/api/v1/raw/notes/"+strings.TrimPrefix(result.Link, "./"), "", nil)
|
|
data, err := io.ReadAll(res.Body)
|
|
if res.StatusCode != 200 || err != nil || string(data) != "image" {
|
|
t.Fatalf("返回链接不能读取原图:%d %q %v", res.StatusCode, data, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestReplaceImageAPI(t *testing.T) {
|
|
f := newFixture(t)
|
|
f.write("assets/a.png", "original")
|
|
f.write("a.md", "")
|
|
var body bytes.Buffer
|
|
form := multipart.NewWriter(&body)
|
|
file, err := form.CreateFormFile("file", "replacement.png")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := io.WriteString(file, "replacement"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := form.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
headers := map[string]string{"Content-Type": form.FormDataContentType()}
|
|
if code := f.status(newClient(), "PUT", "/api/v1/assets/assets/a.png", body.String(), headers); code != 401 {
|
|
t.Fatalf("未登录应被拒绝:%d", code)
|
|
}
|
|
readToken := decode[struct{ Token string }](t, f.do(f.admin, "POST", "/api/v1/tokens", `{"name":"read","scope":"read"}`, nil)).Token
|
|
if code := f.status(newClient(), "PUT", "/api/v1/assets/assets/a.png", body.String(), map[string]string{"Content-Type": form.FormDataContentType(), "Authorization": "Bearer " + readToken}); code != 403 {
|
|
t.Fatalf("只读令牌不能替换图片:%d", code)
|
|
}
|
|
for route, want := range map[string]int{"/api/v1/assets/assets/a.png": 200, "/api/v1/assets/missing.png": 404, "/api/v1/assets/a.md": 400} {
|
|
if code := f.status(f.admin, "PUT", route, body.String(), headers); code != want {
|
|
t.Fatalf("%s:得到 %d,期望 %d", route, code, want)
|
|
}
|
|
}
|
|
res := f.do(f.admin, "GET", "/api/v1/raw/assets/a.png", "", nil)
|
|
data, err := io.ReadAll(res.Body)
|
|
if err != nil || string(data) != "replacement" || res.Header.Get("Content-Security-Policy") == "" || res.Header.Get("Cache-Control") != "no-store" {
|
|
t.Fatalf("替换内容或安全边界错误:%q %v", data, err)
|
|
}
|
|
note := f.do(f.admin, "GET", "/api/v1/files/a.md", "", nil)
|
|
data, err = io.ReadAll(note.Body)
|
|
if err != nil || string(data) != "" {
|
|
t.Fatalf("替换不应改变引用:%q %v", data, err)
|
|
}
|
|
// 复用现有移动与回收站接口,图片不会被当作 Markdown 正文读取。
|
|
if code := f.status(f.admin, "POST", "/api/v1/move", `{"from":"assets/a.png","to":"assets/b.png"}`, nil); code != 200 {
|
|
t.Fatal(code)
|
|
}
|
|
deleted := decode[struct{ ID string }](t, f.do(f.admin, "DELETE", "/api/v1/files/assets/b.png", "", nil))
|
|
if code := f.status(f.admin, "POST", "/api/v1/trash/"+deleted.ID+"/restore", "", nil); code != http.StatusOK {
|
|
t.Fatal(code)
|
|
}
|
|
}
|