Skip to content
quarry Artifact [dea6ee9f8f]
Login

Artifact [dea6ee9f8f]

Artifact dea6ee9f8fdb9e26faff0b5e1a152c9c91c6423e0d877df43e85aab8543199c2:


package markup

import (
	"strings"
	"testing"
)

// Every renderer sends an external image through the proxy, hands the
// proxy the author's URL unescaped, and leaves relative images alone.
func TestExternalImagesGoThroughTheProxy(t *testing.T) {
	var seen []string
	SetImageProxy(func(src string) (string, int, int) {
		seen = append(seen, src)
		if strings.HasPrefix(src, "https://skip.example/") {
			return "", 0, 0
		}
		if strings.Contains(src, "sized") {
			return "/img/sig/" + src, 120, 20
		}
		return "/img/sig/" + src, 0, 0
	})
	t.Cleanup(func() { SetImageProxy(nil) })

	md, err := Render([]byte("![build](https://ci.example/badge.svg?branch=main&x=1) ![local](img/shot.png) ![kept](https://skip.example/a.png)"), nil)
	if err != nil {
		t.Fatal(err)
	}
	got := string(md)
	if !strings.Contains(got, `src="/img/sig/https://ci.example/badge.svg?branch=main&x=1"`) {
		t.Errorf("markdown image not proxied: %s", got)
	}
	if !strings.Contains(got, `src="img/shot.png"`) || !strings.Contains(got, `src="https://skip.example/a.png"`) {
		t.Errorf("a relative or refused image was rewritten: %s", got)
	}
	if len(seen) == 0 || seen[0] != "https://ci.example/badge.svg?branch=main&x=1" {
		t.Errorf("the proxy was handed %q, not the author's URL", seen)
	}

	wiki, err := RenderFossilWiki([]byte(`<img src="https://ci.example/w.png">`), nil)
	if err != nil {
		t.Fatal(err)
	}
	if !strings.Contains(string(wiki), `src="/img/sig/https://ci.example/w.png"`) {
		t.Errorf("fossil wiki image not proxied: %s", wiki)
	}
	if html := Sanitize(`<img src="http://ci.example/h.png">`); !strings.Contains(html, `src="/img/sig/http://ci.example/h.png"`) {
		t.Errorf("html document image not proxied: %s", html)
	}
}

// Off, nothing is rewritten.
func TestNoProxyLeavesImagesAlone(t *testing.T) {
	SetImageProxy(nil)
	out, err := Render([]byte("![b](https://ci.example/badge.svg)"), nil)
	if err != nil {
		t.Fatal(err)
	}
	if !strings.Contains(string(out), `src="https://ci.example/badge.svg"`) {
		t.Errorf("rewritten with no proxy: %s", out)
	}
}

// What the proxy knows goes into the tag: the size it measured, so the
// page does not reflow, and lazy loading for every image after the first
// — the first is usually what the reader came to see.
func TestProxiedImagesCarrySizeAndLazyLoading(t *testing.T) {
	SetImageProxy(func(src string) (string, int, int) {
		if strings.Contains(src, "unsized") {
			return "/img/sig/" + src, 0, 0
		}
		return "/img/sig/" + src, 120, 20
	})
	t.Cleanup(func() { SetImageProxy(nil) })

	out, err := Render([]byte(
		"![one](https://ci.example/a.png)\n\n![two](https://ci.example/b.png)\n\n![three](https://ci.example/unsized.png)"), nil)
	if err != nil {
		t.Fatal(err)
	}
	got := string(out)
	imgs := strings.Split(got, "<img ")[1:]
	if len(imgs) != 3 {
		t.Fatalf("want 3 images, got %d: %s", len(imgs), got)
	}
	if !strings.Contains(imgs[0], `width="120" height="20"`) {
		t.Errorf("the first image has no measured size: %s", imgs[0])
	}
	if strings.Contains(imgs[0], "loading=") || strings.Contains(imgs[0], "decoding=") {
		t.Errorf("the first image was made lazy: %s", imgs[0])
	}
	for i, img := range imgs[1:] {
		if !strings.Contains(img, `loading="lazy"`) || !strings.Contains(img, `decoding="async"`) {
			t.Errorf("image %d is not lazy: %s", i+2, img)
		}
	}
	if strings.Contains(imgs[2], "width=") {
		t.Errorf("an unmeasured image was given a size: %s", imgs[2])
	}
}

// An author who wrote their own width, height or loading keeps it.
func TestAuthorAttributesAreNotOverwritten(t *testing.T) {
	SetImageProxy(func(src string) (string, int, int) { return "/img/sig/" + src, 120, 20 })
	t.Cleanup(func() { SetImageProxy(nil) })
	out := Sanitize(`<img src="https://ci.example/a.png" width="40" height="10"><img src="https://ci.example/b.png" loading="lazy">`)
	imgs := strings.Split(out, "<img ")[1:]
	if len(imgs) != 2 {
		t.Fatalf("want 2 images, got %d: %s", len(imgs), out)
	}
	// Sized by its author: the measured size is not added on top.
	if !strings.Contains(imgs[0], `width="40" height="10"`) || strings.Contains(imgs[0], `width="120"`) {
		t.Errorf("the author's size was replaced: %s", imgs[0])
	}
	// Lazy by its author, and unsized: it keeps the one and is given the
	// other, each exactly once.
	if strings.Count(imgs[1], `loading="lazy"`) != 1 {
		t.Errorf("loading was written twice: %s", imgs[1])
	}
	if !strings.Contains(imgs[1], `width="120" height="20"`) {
		t.Errorf("an unsized image was not measured: %s", imgs[1])
	}
}