86 lines
2.8 KiB
Go
86 lines
2.8 KiB
Go
package preview
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestHTMLMetadata(t *testing.T) {
|
|
u, _ := url.Parse("https://site.test/docs/page")
|
|
p := ParseHTML(strings.NewReader(`<META content='A & B' property='og:title'><meta property="og:image" content="/cover.png"><link href="../icon.png" rel="shortcut icon">`), u)
|
|
if p.OgTitle == nil || *p.OgTitle != "A & B" || p.OgImage == nil || *p.OgImage != "https://site.test/cover.png" || p.Favicon == nil || *p.Favicon != "https://site.test/icon.png" {
|
|
t.Fatalf("metadata: %+v", p)
|
|
}
|
|
}
|
|
|
|
// Remote metadata is clipped so a hostile page cannot bloat every bookmark
|
|
// list response with a megabyte-long title or description.
|
|
func TestHTMLMetadataIsBounded(t *testing.T) {
|
|
u, _ := url.Parse("https://site.test/")
|
|
title := strings.Repeat("T", 1000)
|
|
desc := strings.Repeat("D", 5000)
|
|
p := ParseHTML(strings.NewReader(`<html><head><title>`+title+`</title><meta name="description" content="`+desc+`"></head></html>`), u)
|
|
if p.Title == nil || len([]rune(*p.Title)) != 500 {
|
|
t.Fatalf("title not clipped: %v", p.Title)
|
|
}
|
|
if p.Description == nil || len([]rune(*p.Description)) != 2000 {
|
|
t.Fatalf("description not clipped: %v", p.Description)
|
|
}
|
|
}
|
|
|
|
func TestPrivatePreviewIsBlocked(t *testing.T) {
|
|
if c, e := publicDial(context.Background(), "tcp", "127.0.0.1:5432"); e == nil {
|
|
c.Close()
|
|
t.Fatal("private destination accepted")
|
|
}
|
|
}
|
|
|
|
// Review CR-06: every preview entry point draws from ONE shared budget — a
|
|
// per-identity rate and a global concurrency cap. Exhaustion must deny
|
|
// immediately (callers then persist bookmarks without preview metadata)
|
|
// instead of queueing outbound work.
|
|
func TestSharedBudget(t *testing.T) {
|
|
stamp := time.Now().UnixNano()
|
|
key := fmt.Sprintf("user:rate-%d", stamp)
|
|
for i := 0; i < keyLimit; i++ {
|
|
release, ok := Reserve(key)
|
|
if !ok {
|
|
t.Fatalf("reservation %d within the per-key limit denied", i)
|
|
}
|
|
release()
|
|
}
|
|
if _, ok := Reserve(key); ok {
|
|
t.Fatal("per-key rate budget exceeded")
|
|
}
|
|
// An unrelated identity is unaffected by the exhausted key.
|
|
if release, ok := Reserve(key + "-other"); !ok {
|
|
t.Fatal("independent key denied")
|
|
} else {
|
|
release()
|
|
}
|
|
// Global concurrency: held (unreleased) slots cap all identities.
|
|
var releases []func()
|
|
for i := 0; i < maxConcurrent; i++ {
|
|
release, ok := Reserve(fmt.Sprintf("user:conc-%d-%d", stamp, i))
|
|
if !ok {
|
|
t.Fatalf("concurrency slot %d denied", i)
|
|
}
|
|
releases = append(releases, release)
|
|
}
|
|
if _, ok := Reserve(fmt.Sprintf("user:conc-overflow-%d", stamp)); ok {
|
|
t.Fatal("global concurrency cap not enforced")
|
|
}
|
|
for _, release := range releases {
|
|
release()
|
|
}
|
|
if release, ok := Reserve(fmt.Sprintf("user:conc-after-%d", stamp)); !ok {
|
|
t.Fatal("slot not returned after release")
|
|
} else {
|
|
release()
|
|
}
|
|
}
|