This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user