test(12-05): bring the Phase 12 framework packages to full unit coverage

- lagoon: Go-typed request values (typed slices and maps, sized integers,
  floats, file pointers, typed path lookups), regex delimiters, mimes
  sniffing (jpg/jpeg, SVG, PHP names, unreadable content),
  UploadedFileFromHeader, the rule builders, custom catalog lines with
  :input/:index/:position, and exists: against Postgres (text compare,
  inferred and NULL columns, arrays, unsafe identifiers, missing tables)
- lagoon/attach: thumbnails in every mode from PNG, GIF and JPEG originals,
  bucket URL normalisation, OpenBucket/Publish refusals, URL helpers and
  static prefix stripping
- tide: part validation and encoding edges, symlinks out of the fixture
  directory, Content-Type handling, every response mask and the coverage
  report helpers

Coverage: lagoon 84.4%, lagoon/attach 87.7%, tide 81.4%, beachcomber
84.3%, beachcomber/typesense 95.9%.
This commit is contained in:
Jakub Zych
2026-10-02 15:54:44 +02:00
parent d1650e8213
commit 6e30624ece
4 changed files with 683 additions and 0 deletions

View File

@@ -294,3 +294,123 @@ func TestNormalizePublicationAlbumDates(t *testing.T) {
t.Fatal("a Z album date must show as a diff")
}
}
// TestMultipartPartEdges covers part validation, file reading and encoding
// edges: file attributes without a file, empty and value-only parts,
// default file names and content types, unicode and quoted names, a part
// that contains the boundary, a missing or tampered file, a file outside the
// fixture directory and the Content-Type handling of prepareRequest.
func TestMultipartPartEdges(t *testing.T) {
dir := t.TempDir()
png := []byte("\x89PNG\r\n\x1a\nfixture")
if err := os.MkdirAll(filepath.Join(dir, "files"), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "files", "okładka.png"), png, 0o600); err != nil {
t.Fatal(err)
}
sum := sha256.Sum256(png)
pin := hex.EncodeToString(sum[:])
for name, req := range map[string]Request{
"file attributes without a file": {Parts: []Part{{Name: "a", Filename: "x.png"}}},
"content type without a file": {Parts: []Part{{Name: "a", ContentType: "image/png"}}},
"sha without a file": {Parts: []Part{{Name: "a", SHA256: pin}}},
"blank name": {Parts: []Part{{Name: " ", Value: "x"}}},
"absolute file": {Parts: []Part{{Name: "a", File: "/etc/passwd", SHA256: pin}}},
"short sha": {Parts: []Part{{Name: "a", File: "files/okładka.png", SHA256: "abc"}}},
} {
if err := validateParts(req); err == nil {
t.Errorf("%s: validateParts accepted it", name)
}
}
if err := validateParts(Request{}); err != nil {
t.Fatalf("no parts: %v", err)
}
parts := []Part{
{Name: "empty", Value: ""},
{Name: `tit"le`, Value: "Łódź"},
{Name: "file", File: "files/okładka.png", SHA256: strings.ToUpper(pin)},
}
body, ct, err := encodeParts(dir, parts)
if err != nil {
t.Fatal(err)
}
if ct != "multipart/form-data; boundary="+MultipartBoundary {
t.Fatalf("content type %q", ct)
}
text := string(body)
for _, want := range []string{
`Content-Disposition: form-data; name="empty"`,
`Content-Disposition: form-data; name="tit\"le"` + "\r\n\r\nŁódź",
`Content-Disposition: form-data; name="file"; filename="okładka.png"`,
"Content-Type: application/octet-stream",
} {
if !strings.Contains(text, want) {
t.Errorf("encoded body lacks %q:\n%s", want, text)
}
}
again, _, err := encodeParts(dir, parts)
if err != nil || !bytes.Equal(body, again) {
t.Fatal("encoding is not deterministic")
}
if _, _, err := encodeParts(dir, []Part{{Name: "x", Value: "--" + MultipartBoundary}}); err == nil {
t.Error("a value holding the boundary was encoded")
}
for name, p := range map[string]Part{
"missing": {Name: "f", File: "files/nope.png", SHA256: pin},
"tampered": {Name: "f", File: "files/okładka.png", SHA256: strings.Repeat("0", 64)},
"escaping": {Name: "f", File: "../okładka.png", SHA256: pin},
} {
if _, err := readPartFile(dir, p); err == nil {
t.Errorf("%s part file was read", name)
}
}
outside := t.TempDir()
if err := os.WriteFile(filepath.Join(outside, "x.png"), png, 0o600); err != nil {
t.Fatal(err)
}
if err := os.Symlink(filepath.Join(outside, "x.png"), filepath.Join(dir, "files", "link.png")); err != nil {
t.Fatal(err)
}
if _, err := readPartFile(dir, Part{Name: "f", File: "files/link.png", SHA256: pin}); err == nil {
t.Error("a symlink leaving the fixture directory was read")
}
if raw, err := readPartFile("", Part{Name: "f", File: filepath.Join(dir, "files", "okładka.png")[len(dir)+1:], SHA256: pin}); err == nil && raw != nil {
t.Error("a part file resolved without its fixture directory")
}
for name, tc := range map[string]struct {
headers map[string]string
want string
}{
"none recorded": {nil, "multipart/form-data; boundary=" + MultipartBoundary},
"stale boundary": {map[string]string{"content-type": "multipart/form-data; boundary=old"}, "multipart/form-data; boundary=" + MultipartBoundary},
"unparsable": {map[string]string{"Content-Type": ";;;"}, "multipart/form-data; boundary=" + MultipartBoundary},
"kept non-multipart": {map[string]string{"Content-Type": "text/plain"}, "text/plain"},
} {
out, err := prepareRequest(Request{Method: "POST", Headers: tc.headers, Parts: parts[:1]}, dir)
if err != nil {
t.Fatal(err)
}
got := ""
for k, v := range out.Headers {
if strings.EqualFold(k, "Content-Type") {
got = v
}
}
if got != tc.want {
t.Errorf("%s: Content-Type %q, want %q", name, got, tc.want)
}
}
if out, err := prepareRequest(Request{Body: "a=1"}, dir); err != nil || out.Body != "a=1" {
t.Fatalf("no parts: %+v %v", out, err)
}
if _, err := prepareRequest(Request{Body: "a=1", Parts: parts[:1]}, dir); err == nil {
t.Fatal("body and parts were prepared")
}
if _, err := prepareRequest(Request{Parts: []Part{{Name: "f", File: "files/nope.png", SHA256: pin}}}, dir); err == nil {
t.Fatal("a missing part file was prepared")
}
}