// Package blossom tests cover the pure request-routing surface: the hex/extension // tables, the upload handler and the path gate. The HTTP transport itself lives // in pkg/relay/wire and is out of scope here. // // A former compiler defect is fixed and the cases it hid are asserted again: // `range` over a string loaded its element as a 4-byte word (the element type // defaulted to i32 and was narrowed only for a slice), which made isHex reject // every real hash - every blob GET/HEAD answered 404 - and made // net/url.validOptionalPort reject a valid `host:port`, so an upstream with a // port read as self. stage4 now loads a byte and zero-extends it (see the // commit "one write per coverage line, and a byte-sized range over a string"). package blossom import ( "os" "testing" ) // blTmp creates a server over a fresh directory. The caller owns both. func blTmp(t *testing.T) (s *Server, dir string) { t.Helper() dir, derr := os.MkdirTemp("", "moxie-blossom") if derr != nil { t.Fatal(derr) } s, serr := New(dir) if serr != nil { t.Fatal(serr) } return s, dir } // TestIsHex pins the hex test every blob path depends on. func TestIsHex(t *testing.T) { if !isHex("") { t.Fatal("isHex(\"\") must be vacuously true") } if !isHex("a") { t.Fatal("single hex letter rejected") } if !isHex("0") { t.Fatal("single digit rejected") } if isHex("g") { t.Fatal("single non-hex letter accepted") } if !isHex("0123456789") { t.Fatal("digit string rejected") } if !isHex("abcdefABCDEF") { t.Fatal("mixed-case hex string rejected") } if !isHex("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") { t.Fatal("a 64-character hash rejected") } if isHex("0123456789abcdefg") { t.Fatal("a non-hex character accepted") } } // TestMimeFromExt pins the extension to content-type table and the binary // fallback. func TestMimeFromExt(t *testing.T) { cases := []struct { ext string want string }{ {"png", "image/png"}, {"jpg", "image/jpeg"}, {"jpeg", "image/jpeg"}, {"gif", "image/gif"}, {"webp", "image/webp"}, {"svg", "image/svg+xml"}, {"pdf", "application/pdf"}, {"mp4", "video/mp4"}, {"webm", "video/webm"}, {"mov", "video/quicktime"}, {"mkv", "video/x-matroska"}, {"avi", "video/x-msvideo"}, {"mp3", "audio/mpeg"}, {"ogg", "audio/ogg"}, {"m4a", "audio/mp4"}, {"", "application/octet-stream"}, {"txt", "application/octet-stream"}, {"PNG", "application/octet-stream"}, } for _, c := range cases { got := mimeFromExt(c.ext) if got != c.want { t.Fatalf("mimeFromExt(%q) = %q, want %q", c.ext, got, c.want) } } } // TestExtFromMime pins the reverse table and that an unknown type yields no // extension (so the upload URL is just the hash). func TestExtFromMime(t *testing.T) { cases := []struct { mime string want string }{ {"image/png", ".png"}, {"image/jpeg", ".jpg"}, {"image/jpg", ".jpg"}, {"image/gif", ".gif"}, {"image/webp", ".webp"}, {"image/svg+xml", ".svg"}, {"video/mp4", ".mp4"}, {"video/webm", ".webm"}, {"video/quicktime", ".mov"}, {"video/x-matroska", ".mkv"}, {"video/x-msvideo", ".avi"}, {"audio/mpeg", ".mp3"}, {"audio/ogg", ".ogg"}, {"audio/mp4", ".m4a"}, {"text/plain", ""}, {"", ""}, } for _, c := range cases { got := extFromMime(c.mime) if got != c.want { t.Fatalf("extFromMime(%q) = %q, want %q", c.mime, got, c.want) } } } // TestIsSelfUpstream pins the proxy loop guard: only a URL with a parseable // host is treated as a real upstream. The host:port case is omitted because // net/url.validOptionalPort hits the same range-over-string defect and reports // a valid port as invalid (see the file-head defect). func TestIsSelfUpstream(t *testing.T) { if !isSelfUpstream("") { t.Fatal("empty upstream must read as self") } if !isSelfUpstream("not a url") { t.Fatal("unparseable upstream must read as self") } if !isSelfUpstream("/relative/path") { t.Fatal("hostless relative upstream must read as self") } if isSelfUpstream("https://relay.example.com") { t.Fatal("absolute upstream must not read as self") } if isSelfUpstream("http://relay.example.com:8080/blossom/") { t.Fatal("an absolute upstream with a port read as self") } if isSelfUpstream("https://relay.example.com:443") { t.Fatal("an https upstream with a port read as self") } } // TestHandleRawPathValidation pins the path gate: OPTIONS is answered without // touching disk, and a GET whose hash is not exactly 64 hex characters is a // 404 with CORS headers. func TestHandleRawPathValidation(t *testing.T) { s, dir := blTmp(t) defer os.RemoveAll(dir) status, headers, body := s.HandleRawWithUpstream("OPTIONS", "/anything", nil, nil, "") if status != 204 { t.Fatalf("OPTIONS status = %d, want 204", status) } if body != nil { t.Fatal("OPTIONS must have no body") } if headers["Access-Control-Allow-Origin"] != "*" { t.Fatal("OPTIONS must carry CORS headers") } short := "0123456789abcdef" status2, _, _ := s.HandleRawWithUpstream("GET", "/"|short, nil, nil, "") if status2 != 404 { t.Fatalf("short hash status = %d, want 404", status2) } nonHex := "" for i := 0; i < 64; i++ { nonHex = nonHex | "z" } status3, _, _ := s.HandleRawWithUpstream("GET", "/"|nonHex, nil, nil, "") if status3 != 404 { t.Fatalf("non-hex hash status = %d, want 404", status3) } // A dot at position 0 leaves a four-character hash and must 404. status4, _, _ := s.HandleRawWithUpstream("GET", "/.png", nil, nil, "") if status4 != 404 { t.Fatalf("dot-only path status = %d, want 404", status4) } // 64 hex chars, local miss, no upstream: a clean 404 with CORS headers. miss := "aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899" status5, h5, _ := s.HandleRawWithUpstream("GET", "/"|miss, nil, nil, "") if status5 != 404 { t.Fatalf("missing blob status = %d, want 404", status5) } if h5["Access-Control-Allow-Origin"] != "*" { t.Fatal("404 must carry CORS headers") } } // TestHandleUpload pins the upload contract: empty body is 400, a real body is // written under the sha256 of its content and answered with that hash in the // JSON body, with the extension chosen from the content type. func TestHandleUpload(t *testing.T) { s, dir := blTmp(t) defer os.RemoveAll(dir) status, _, _ := s.HandleRawWithUpstream("PUT", "/upload", nil, nil, "") if status != 400 { t.Fatalf("empty upload status = %d, want 400", status) } body := []byte("hello") headers := map[string]string{"content-type": "text/plain"} status2, h2, resp := s.HandleRawWithUpstream("PUT", "/upload", headers, body, "") if status2 != 200 { t.Fatalf("upload status = %d, want 200", status2) } if h2["Content-Type"] != "application/json" { t.Fatalf("upload response type = %q", h2["Content-Type"]) } wantHash := "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824" want := "{\"url\":\"/blossom/" | wantHash | "\",\"sha256\":\"" | wantHash | "\",\"size\":5,\"type\":\"text/plain\"}" if string(resp) != want { t.Fatalf("upload body = %s, want %s", string(resp), want) } if _, err := os.Stat(s.dir|"/"|wantHash); err != nil { t.Fatal("uploaded body was not written to disk") } // A known image type adds the matching extension to the returned URL. status3, _, resp3 := s.HandleRawWithUpstream("PUT", "/upload", map[string]string{"content-type": "image/png"}, []byte("png bytes"), "") if status3 != 200 { t.Fatalf("png upload status = %d", status3) } if !blHas(resp3, []byte(".png\"")) { t.Fatalf("png upload missing .png extension: %s", string(resp3)) } if !blHas(resp3, []byte("\"url\":\"/blossom/")) { t.Fatalf("png upload missing url: %s", string(resp3)) } } // blHas is a local substring scan; bytes.Contains routes long haystacks through // bytes.Index's Rabin-Karp fallback, which misses a present needle (reported // separately). func blHas(hay, needle []byte) (ok bool) { for i := 0; i+len(needle) <= len(hay); i++ { if string(hay[i:i+len(needle)]) == string(needle) { return true } } return false }