Files
sm-bm-plugin/controllers/api/uploads_test.go
Jakub Zych 93066c63ea feat: attach material files over MCP as base64
- storeMaterialFile: the one attach.Store call of the management API,
  shared by the multipart route and the new inline route
- POST /api/v1/bm/materials/{id}/files/inline: JSON {name, mime?,
  content_base64}, at most 3000000 bytes decoded, body.limit 4300000
- bm_manage_attach_material_file and bm_manage_detach_material_file,
  both warning that material files are public to anyone with the link
2026-10-28 15:03:43 +01:00

91 lines
3.7 KiB
Go

package api_test
import (
"encoding/base64"
"net/http"
"strings"
"testing"
"git.golem15.com/golem15/summercms/modules/bouncer"
"git.golem15.com/jakub/sm-bm-plugin/controllers/api"
"gorm.io/gorm"
)
// TestDecodeInline (D-11, D-18) checks the body of POST
// /materials/{id}/files/inline before any database work.
func TestDecodeInline(t *testing.T) {
pdf := []byte("%PDF-1.4\n%notatki\n")
enc := base64.StdEncoding.EncodeToString(pdf)
t.Run("valid", func(t *testing.T) {
name, mime, data, errs := api.DecodeInline([]byte(`{"name":" notatki.pdf ","mime":"Application/PDF","content_base64":"` + enc + `"}`))
if errs != nil {
t.Fatalf("errors %v", errs)
}
if name != "notatki.pdf" || mime != "application/pdf" || string(data) != string(pdf) {
t.Errorf("got %q %q %q", name, mime, data)
}
})
t.Run("mime optional", func(t *testing.T) {
_, mime, data, errs := api.DecodeInline([]byte(`{"name":"a.pdf","content_base64":"` + enc + `"}`))
if errs != nil || mime != "" || len(data) != len(pdf) {
t.Errorf("got mime %q, %d bytes, errors %v", mime, len(data), errs)
}
})
big := strings.Repeat("A", base64.StdEncoding.EncodedLen(api.MaterialInlineMaxBytes)+4)
cases := []struct {
name, body, field, contains string
}{
{"missing name", `{"content_base64":"` + enc + `"}`, "name", ""},
{"blank name", `{"name":" ","content_base64":"` + enc + `"}`, "name", ""},
{"empty content", `{"name":"a.pdf","content_base64":""}`, "content_base64", ""},
{"missing content", `{"name":"a.pdf"}`, "content_base64", ""},
{"invalid base64", `{"name":"a.pdf","content_base64":"%%%not base64"}`, "content_base64", "base64"},
{"url-safe alphabet", `{"name":"a.pdf","content_base64":"-_-_"}`, "content_base64", "base64"},
{"unknown key", `{"name":"a.pdf","content_base64":"` + enc + `","path":"/etc/passwd"}`, "json", ""},
{"not an object", `[1,2]`, "json", ""},
{"bad mime", `{"name":"a.pdf","mime":"pdf","content_base64":"` + enc + `"}`, "mime", ""},
{"too large", `{"name":"a.pdf","content_base64":"` + big + `"}`, "content_base64", "bm_manage_create_material_upload"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
_, _, data, errs := api.DecodeInline([]byte(tc.body))
if data != nil {
t.Errorf("data returned with an invalid body")
}
msgs := errs[tc.field]
if len(msgs) == 0 {
t.Fatalf("errors %v have no %s entry", errs, tc.field)
}
if tc.contains != "" && !strings.Contains(msgs[0], tc.contains) {
t.Errorf("%s message %q does not name %q", tc.field, msgs[0], tc.contains)
}
})
}
}
// TestAttachMaterialInlineNoDatabase: an invalid body is a 422 before the
// database is looked up; a valid one without a database is a 503.
func TestAttachMaterialInlineNoDatabase(t *testing.T) {
user := &bouncer.Principal{ID: 7}
enc := base64.StdEncoding.EncodeToString([]byte("%PDF-1.4\n"))
valid := bodyRequest{noDB().AttachMaterialInline, http.MethodPost, "/", `{"name":"a.pdf","content_base64":"` + enc + `"}`}
if code, body := serveBody(t, valid, user); code != http.StatusServiceUnavailable {
t.Errorf("valid body without a database = %d %v, want 503", code, body)
}
dbLooked := false
h := &api.Handlers{DB: func() *gorm.DB { dbLooked = true; return nil }}
invalid := bodyRequest{h.AttachMaterialInline, http.MethodPost, "/", `{"name":"","content_base64":""}`}
code, body := serveBody(t, invalid, user)
if code != http.StatusUnprocessableEntity {
t.Errorf("invalid body = %d %v, want 422", code, body)
}
if errs, _ := body["errors"].(map[string]any); errs["name"] == nil || errs["content_base64"] == nil {
t.Errorf("errors %v lack name and content_base64", body["errors"])
}
if dbLooked {
t.Error("the database was looked up for an invalid body")
}
}