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") } }