From 4883820f2521280b824dbedf77c89f82070f96cb Mon Sep 17 00:00:00 2001 From: Masahiko AMANO Date: Fri, 3 Jul 2026 08:34:59 +0300 Subject: [PATCH] test(backend): encode the JPEG test fixture so it decodes minimalJPEG() returned a hand-written byte literal that passed MIME sniffing but failed image.Decode ("missing SOS marker"), so the upload path left phash NULL and TestDuplicateDetection found no duplicate clusters. Build the fixture with image/jpeg.Encode instead, so it decodes through the same path the server uses to hash uploads. Encoding is deterministic, so two uploads still hash identically. Co-Authored-By: Claude Opus 4.8 --- backend/internal/integration/server_test.go | 52 ++++++++------------- 1 file changed, 19 insertions(+), 33 deletions(-) diff --git a/backend/internal/integration/server_test.go b/backend/internal/integration/server_test.go index 0b102af..e1a2e7f 100644 --- a/backend/internal/integration/server_test.go +++ b/backend/internal/integration/server_test.go @@ -16,6 +16,8 @@ import ( "context" "encoding/json" "fmt" + "image" + "image/jpeg" "io" "mime/multipart" "net" @@ -1575,41 +1577,25 @@ func TestPoolOperationsRequireACL(t *testing.T) { // Test helpers // --------------------------------------------------------------------------- -// minimalJPEG returns the bytes of a 1×1 white JPEG image. -// Generated offline; no external dependency needed. +// minimalJPEG returns the bytes of a small solid-white JPEG produced by the +// standard library encoder. Encoding is deterministic, so every call yields +// byte-identical output — two uploads therefore hash to the same perceptual +// hash (a solid image dHashes to 0), which is what the duplicate test relies on. +// +// It is encoded (rather than a hand-written byte literal) so the bytes decode +// cleanly through image.Decode: that is the same path FileService.Upload takes +// to compute the perceptual hash, and a fixture that only passes MIME sniffing +// but fails to fully decode would leave phash NULL and silently break dedup. func minimalJPEG() []byte { - // This is a valid minimal JPEG: SOI + APP0 + DQT + SOF0 + DHT + SOS + EOI. - // 1×1 white pixel, quality ~50. Verified with `file` and browsers. - return []byte{ - 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, - 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0xff, 0xdb, 0x00, 0x43, - 0x00, 0x08, 0x06, 0x06, 0x07, 0x06, 0x05, 0x08, 0x07, 0x07, 0x07, 0x09, - 0x09, 0x08, 0x0a, 0x0c, 0x14, 0x0d, 0x0c, 0x0b, 0x0b, 0x0c, 0x19, 0x12, - 0x13, 0x0f, 0x14, 0x1d, 0x1a, 0x1f, 0x1e, 0x1d, 0x1a, 0x1c, 0x1c, 0x20, - 0x24, 0x2e, 0x27, 0x20, 0x22, 0x2c, 0x23, 0x1c, 0x1c, 0x28, 0x37, 0x29, - 0x2c, 0x30, 0x31, 0x34, 0x34, 0x34, 0x1f, 0x27, 0x39, 0x3d, 0x38, 0x32, - 0x3c, 0x2e, 0x33, 0x34, 0x32, 0xff, 0xc0, 0x00, 0x0b, 0x08, 0x00, 0x01, - 0x00, 0x01, 0x01, 0x01, 0x11, 0x00, 0xff, 0xc4, 0x00, 0x1f, 0x00, 0x00, - 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - 0x09, 0x0a, 0x0b, 0xff, 0xc4, 0x00, 0xb5, 0x10, 0x00, 0x02, 0x01, 0x03, - 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, - 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, - 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, - 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, - 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, - 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, - 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, - 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, - 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, - 0x8a, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, - 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, - 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, - 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, - 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, - 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xff, 0xda, 0x00, 0x08, 0x01, 0x01, 0x00, - 0x00, 0x3f, 0x00, 0xfb, 0xd3, 0xff, 0xd9, + img := image.NewGray(image.Rect(0, 0, 16, 16)) + for i := range img.Pix { + img.Pix[i] = 0xff // white } + var buf bytes.Buffer + if err := jpeg.Encode(&buf, img, &jpeg.Options{Quality: 75}); err != nil { + panic("minimalJPEG: encode: " + err.Error()) + } + return buf.Bytes() } // replaceDSNDatabase returns a copy of dsn with the dbname parameter replaced.