MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m52s
- GET /link-preview?url= fetches the page on the server (https only, SSRF-guarded like webhooks), reads Open Graph/Twitter tags, title, description and icon from the head, loads picture and icon (no SVG) and keeps them in link_previews (migration 6) for 7 days; readers' browsers never contact the page. Off in the demo; 300 fetches per address in 15 minutes. - Compact card above the bullet (icon, site, title, one line, small picture); the duplicate link badge is left out. - ✕ turns the address into [title](url), so hiding syncs with the note; "Link-Vorschauen anzeigen" under appearance switches all off per device. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
59 lines
2.7 KiB
Go
59 lines
2.7 KiB
Go
package api
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/sandrodach/mindsift/internal/storage"
|
|
)
|
|
|
|
func TestParsePageMeta(t *testing.T) {
|
|
doc := `<!doctype html><html><head>
|
|
<meta charset="utf-8"><title>Fallback Titel</title>
|
|
<meta property="og:title" content="Rezept: Käsespätzle & Salat">
|
|
<meta name="description" content="Schnell gemacht,
|
|
für 4 Personen.">
|
|
<meta property='og:image' content='/img/spaetzle.jpg'>
|
|
<meta property="og:site_name" content="Kochseite">
|
|
<link rel="icon" href="/favicon.png"><link rel="stylesheet" href="x.css">
|
|
</head><body><meta property="og:title" content="nicht im Kopf"></body></html>`
|
|
m := parsePageMeta(doc)
|
|
if m.title != "Rezept: Käsespätzle & Salat" || m.description != "Schnell gemacht, für 4 Personen." ||
|
|
m.siteName != "Kochseite" || m.image != "/img/spaetzle.jpg" || m.icon != "/favicon.png" {
|
|
t.Fatalf("unexpected meta: %+v", m)
|
|
}
|
|
if m := parsePageMeta(`<head><title> Nur Titel </title></head>`); m.title != "Nur Titel" {
|
|
t.Fatalf("title fallback: %+v", m)
|
|
}
|
|
if long := cleanText(strings.Repeat("a", 500), 200); len([]rune(long)) != 200 || !strings.HasSuffix(long, "…") {
|
|
t.Fatalf("long text not shortened: %d", len(long))
|
|
}
|
|
}
|
|
|
|
func TestLinkPreviewEndpoints(t *testing.T) {
|
|
srv, store, token := setupTestServer(t)
|
|
router := srv.Router()
|
|
if rec := sessionCall(router, "GET", "/api/v1/link-preview?url=http://example.com", token, "", ""); rec.Code != http.StatusUnprocessableEntity {
|
|
t.Fatalf("http link: want 422, got %d", rec.Code)
|
|
}
|
|
// A cached preview is answered without fetching; its pictures are served with the login
|
|
p := &storage.LinkPreview{URL: "https://example.com/", Title: "Beispiel", OK: true, Icon: []byte("\x89PNG\r\n\x1a\n"), IconMime: "image/png"}
|
|
if err := store.SaveLinkPreview(p); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rec := sessionCall(router, "GET", "/api/v1/link-preview?url=https://example.com/", token, "", "")
|
|
if rec.Code != http.StatusOK || !strings.Contains(rec.Body.String(), `"title":"Beispiel"`) || !strings.Contains(rec.Body.String(), `"hasIcon":true`) {
|
|
t.Fatalf("cached preview: %d %s", rec.Code, rec.Body.String())
|
|
}
|
|
if rec := sessionCall(router, "GET", "/api/v1/link-preview/"+p.Key+"/icon", token, "", ""); rec.Code != http.StatusOK || rec.Header().Get("Content-Type") != "image/png" {
|
|
t.Fatalf("icon: %d", rec.Code)
|
|
}
|
|
if rec := sessionCall(router, "GET", "/api/v1/link-preview/"+p.Key+"/image", token, "", ""); rec.Code != http.StatusNotFound {
|
|
t.Fatalf("missing image: want 404, got %d", rec.Code)
|
|
}
|
|
if rec := sessionCall(router, "GET", "/api/v1/link-preview/"+p.Key+"/icon", "", "", ""); rec.Code != http.StatusUnauthorized {
|
|
t.Fatalf("without login: want 401, got %d", rec.Code)
|
|
}
|
|
}
|