all repos — weimar @ link-previews

Unnamed repository; edit this file 'description' to name the repository.

internal/server/server_test.go (view raw)

  1package server
  2
  3import (
  4	"context"
  5	"io"
  6	"net/http"
  7	"net/http/httptest"
  8	"strconv"
  9	"strings"
 10	"testing"
 11
 12	"codeberg.org/maxwelljensen/weimar/internal/auth"
 13	"codeberg.org/maxwelljensen/weimar/internal/db"
 14)
 15
 16func TestHandleOGPreview_ServesAllRequiredTags(t *testing.T) {
 17	database, err := db.Open(":memory:")
 18	if err != nil {
 19		t.Fatalf("open db: %v", err)
 20	}
 21	t.Cleanup(func() { database.Close() })
 22	if err := database.Migrate(context.Background()); err != nil {
 23		t.Fatalf("migrate: %v", err)
 24	}
 25
 26	// Create a user and an image directly.
 27	auth.Register(context.Background(), database, "testuser", "password123", false)
 28	img, err := database.CreateImage(context.Background(), &db.Image{
 29		Filename:    "photo.jpg",
 30		StoragePath: "ab/cd/ef/abcdef.jpg",
 31		UploadedBy:  1,
 32		FileSize:    204800,
 33		Width:       1920,
 34		Height:      1080,
 35		MimeType:    "image/jpeg",
 36	})
 37	if err != nil {
 38		t.Fatalf("create image: %v", err)
 39	}
 40
 41	// Construct a minimal Server with just the fields needed by handleOGPreview.
 42	s := &Server{
 43		db: database,
 44		// spaH is only called on error (missing image); we never hit it in this test.
 45		spaH: func(w http.ResponseWriter, r *http.Request) {},
 46	}
 47
 48	idStr := strconv.FormatInt(img.ID, 10)
 49	req := httptest.NewRequest("GET", "/image/"+idStr, nil)
 50	req.SetPathValue("id", idStr)
 51	w := httptest.NewRecorder()
 52
 53	s.handleOGPreview(w, req)
 54
 55	resp := w.Result()
 56	body, _ := io.ReadAll(resp.Body)
 57	resp.Body.Close()
 58	html := string(body)
 59
 60	if resp.StatusCode != 200 {
 61		t.Fatalf("status = %d, want 200; body:\n%s", resp.StatusCode, html)
 62	}
 63
 64	ct := resp.Header.Get("Content-Type")
 65	if !strings.HasPrefix(ct, "text/html") {
 66		t.Fatalf("Content-Type = %q, want text/html", ct)
 67	}
 68
 69	// Verify all required OG tags are present by content.
 70	required := []string{
 71		`og:title`,
 72		`og:description`,
 73		`og:url`,
 74		`og:image`,
 75		`og:image:secure_url`,
 76		`og:type`,
 77		`og:locale`,
 78		`twitter:card`,
 79		`twitter:image`,
 80	}
 81	for _, tag := range required {
 82		if !strings.Contains(html, tag) {
 83			t.Errorf("missing meta tag: %s", tag)
 84		}
 85	}
 86
 87	// Verify specific values.
 88	if !strings.Contains(html, "photo.jpg") {
 89		t.Errorf("expected filename in OG title, got:\n%s", html)
 90	}
 91	if !strings.Contains(html, "testuser") {
 92		t.Errorf("expected uploader in description, got:\n%s", html)
 93	}
 94	if !strings.Contains(html, "1920") {
 95		t.Errorf("expected width in OG image:width, got:\n%s", html)
 96	}
 97	// For images, og:image should point to the download URL (full-res), not thumbnail.
 98	if !strings.Contains(html, "/download") {
 99		t.Errorf("expected og:image to use download URL (full-res) for images, got:\n%s", html)
100	}
101	if strings.Contains(html, "/thumbnail") {
102		t.Errorf("og:image should use download URL for images, not thumbnail:\n%s", html)
103	}
104	if strings.Contains(html, "&") || strings.Contains(html, "<") {
105		t.Errorf("HTML appears double-escaped:\n%s", html)
106	}
107	// Should NOT contain the SPA title placeholder.
108	if strings.Contains(html, "weimar") && !strings.Contains(html, "og:site_name") {
109		t.Errorf("response appears to be SPA fallback, not OG page:\n%s", html)
110	}
111}