memos/internal/motionphoto/reader_test.go
boojack b3e67399b8 feat(attachment): upload files in resumable chunks
Add UploadAttachment, a unary RPC that accepts a file in bounded chunks
and streams it to a temp file instead of holding the whole blob in one
request body and in memory. A call carrying a spec starts an upload and
returns an opaque upload_id; later calls carry that id with
write_offset,
data, and finish_write, following the Cloud Storage WriteObject shape.
Retrying the last chunk after a lost response is idempotent, uploads are
bound to their owner, and they expire after 30 minutes of inactivity.

CreateAttachment and the chunked finalize now share one processing
pipeline for motion-photo detection, EXIF stripping, and storage. The
motion-photo detector works over an io.ReaderAt so large JPEGs are never
loaded whole, and the request body cap is a single per-procedure lookup.

The web editor uploads through the new RPC in 2 MiB chunks with bounded
concurrency under the server's per-user limit.
2026-09-08 21:07:06 +08:00

45 lines
1.8 KiB
Go

package motionphoto
import (
"bytes"
"testing"
"github.com/stretchr/testify/require"
"github.com/usememos/memos/internal/testutil"
)
func TestDetectJPEGReader(t *testing.T) {
for _, blob := range [][]byte{nil, []byte("not a JPEG"), []byte("\xff\xd8 no motion marker here at all")} {
detected, err := DetectJPEGReader(bytes.NewReader(blob), int64(len(blob)))
require.NoError(t, err)
require.Nil(t, detected)
}
fixture := testutil.BuildMotionPhotoJPEG()
detected, err := DetectJPEGReader(bytes.NewReader(fixture), int64(len(fixture)))
require.NoError(t, err)
require.NotNil(t, detected)
require.True(t, looksLikeMP4(fixture[detected.VideoStart:]))
require.EqualValues(t, 123456, detected.PresentationTimestampUs)
// Exercise MP4 headers straddling both sides of the backwards scan boundary.
for offset := -12; offset <= 12; offset++ {
blob := make([]byte, 200_000)
copy(blob, []byte("\xff\xd8 Camera:MotionPhoto=\"1\""))
start := len(blob) - 64*1024 + offset
copy(blob[start:], []byte{0, 0, 0, 16, 'f', 't', 'y', 'p', 'm', 'p', '4', '2'})
detected, err := DetectJPEGReader(bytes.NewReader(blob), int64(len(blob)))
require.NoError(t, err)
require.NotNil(t, detected, "offset %d", offset)
require.Equal(t, start, detected.VideoStart, "offset %d", offset)
}
// Prefer the explicit MicroVideoOffset even when a later ftyp box exists.
blob := make([]byte, 1024)
copy(blob, []byte("\xff\xd8 Camera:MotionPhoto=\"1\" Camera:MicroVideoOffset=\"128\""))
copy(blob[896:], []byte{0, 0, 0, 16, 'f', 't', 'y', 'p', 'm', 'p', '4', '2'})
copy(blob[1000:], []byte{0, 0, 0, 16, 'f', 't', 'y', 'p', 'm', 'p', '4', '2'})
detected, err = DetectJPEGReader(bytes.NewReader(blob), int64(len(blob)))
require.NoError(t, err)
require.Equal(t, 896, detected.VideoStart)
}