Files
summercms/modules/lagoon/attach/inline_test.go
Jakub Zych 903859a131 feat(lagoon/attach): export UploadWriterOptions for blob writers that bypass Store (quick-261106-d20)
- attach.UploadWriterOptions (was the unexported uploadWriterOptions) gives
  code that writes an upload to the bucket itself the same Content-Type and
  Content-Disposition: attachment for non-inline types that attach.Store stores
- writeBlob calls it; Store behaviour unchanged
- TestUploadWriterOptions table test
- lagoon README, docs/database/attachments.md and docs/services/storage.md
  describe it for writers that bypass attach.Store
2026-11-06 09:31:51 +01:00

156 lines
4.4 KiB
Go

package attach
import (
"reflect"
"strings"
"testing"
"gocloud.dev/blob"
"gocloud.dev/blob/fileblob"
"gocloud.dev/blob/memblob"
)
func TestIsInlineContentType(t *testing.T) {
cases := []struct {
in string
want bool
}{
{"image/jpeg", true},
{"image/png", true},
{"image/gif", true},
{"image/webp", true},
{"image/avif", true},
{"IMAGE/WEBP; charset=binary", true},
{"text/html; charset=utf-8", false},
{"image/svg+xml", false},
{"application/pdf", false},
{"text/plain", false},
{"application/octet-stream", false},
{"application/zip", false},
{"image/bmp", false},
{"image/x-icon", false},
{"video/mp4", false},
{"audio/mpeg", false},
{"", false},
{"not a media type;;", false},
}
for _, tc := range cases {
if got := IsInlineContentType(tc.in); got != tc.want {
t.Errorf("IsInlineContentType(%q) = %v, want %v", tc.in, got, tc.want)
}
}
}
func TestUploadWriterOptions(t *testing.T) {
cases := []struct {
in string
disposition string
}{
{"image/png", ""},
{"image/jpeg", ""},
{"image/gif", ""},
{"image/avif", ""},
{"IMAGE/WEBP; charset=binary", ""},
{"text/html", "attachment"},
{"text/xml", "attachment"},
{"image/svg+xml", "attachment"},
{"application/pdf", "attachment"},
{"text/plain", "attachment"},
{"application/octet-stream", "attachment"},
{"", "attachment"},
}
for _, tc := range cases {
got := UploadWriterOptions(tc.in)
want := &blob.WriterOptions{ContentType: tc.in, ContentDisposition: tc.disposition}
if !reflect.DeepEqual(got, want) {
t.Errorf("UploadWriterOptions(%q) = %+v, want %+v", tc.in, got, want)
}
}
first := UploadWriterOptions("text/html")
second := UploadWriterOptions("text/html")
if first == second {
t.Fatal("UploadWriterOptions returned the same pointer twice")
}
first.ContentDisposition = "inline"
first.CacheControl = "no-store"
if third := UploadWriterOptions("text/html"); third.ContentDisposition != "attachment" || third.CacheControl != "" {
t.Errorf("mutating one result changed the next: %+v", third)
}
}
// TestWriteBlobDisposition is the driver half of the public-bucket fix: a
// public bucket serves the headers Store stores with the object, so the
// attachment disposition must survive the write on the real drivers. The
// metadata-skip fileblob bucket shows that a driver which does not keep
// attributes still accepts the option without an error.
func TestWriteBlobDisposition(t *testing.T) {
cases := []struct {
name, ct, disposition string
}{
{"html", "text/html", "attachment"},
{"text", "text/plain", "attachment"},
{"pdf", "application/pdf", "attachment"},
{"svg", "image/svg+xml", "attachment"},
{"zip", "application/zip", "attachment"},
{"png", "image/png", ""},
{"jpeg", "image/jpeg", ""},
{"avif", "image/avif", ""},
}
sidecar, err := fileblob.OpenBucket(t.TempDir(), nil)
if err != nil {
t.Fatal(err)
}
noMeta, err := fileblob.OpenBucket(t.TempDir(), &fileblob.Options{Metadata: fileblob.MetadataDontWrite})
if err != nil {
t.Fatal(err)
}
buckets := []struct {
name string
bucket *blob.Bucket
keepsAttrs bool
}{
{"memblob", memblob.OpenBucket(nil), true},
{"fileblob sidecar", sidecar, true},
{"fileblob metadata skipped", noMeta, false},
}
const body = "<html><script>alert(1)</script></html>"
for _, b := range buckets {
bucket := b.bucket
t.Cleanup(func() { _ = bucket.Close() })
t.Run(b.name, func(t *testing.T) {
ctx := t.Context()
for _, tc := range cases {
key := "abc/def/012/" + tc.name + "-file.bin"
n, err := writeBlob(ctx, bucket, key, strings.NewReader(body), tc.ct, 0)
if err != nil {
t.Fatalf("%s: writeBlob: %v", tc.name, err)
}
if n != int64(len(body)) {
t.Fatalf("%s: wrote %d bytes, want %d", tc.name, n, len(body))
}
got, err := bucket.ReadAll(ctx, key)
if err != nil {
t.Fatalf("%s: ReadAll: %v", tc.name, err)
}
if string(got) != body {
t.Fatalf("%s: body = %q, want %q", tc.name, got, body)
}
if !b.keepsAttrs {
continue
}
attrs, err := bucket.Attributes(ctx, key)
if err != nil {
t.Fatalf("%s: Attributes: %v", tc.name, err)
}
if attrs.ContentType != tc.ct {
t.Errorf("%s: ContentType = %q, want %q", tc.name, attrs.ContentType, tc.ct)
}
if attrs.ContentDisposition != tc.disposition {
t.Errorf("%s: ContentDisposition = %q, want %q", tc.name, attrs.ContentDisposition, tc.disposition)
}
}
})
}
}