Bluesky linkifies nothing by itself. A URL in the text of a record is plain text unless a richtext facet says which bytes of the post are a link and where they point, so our posts carried URLs no one could press. Each link now travels on the Post as an output.Link with the offsets of the text it occupies, and the bluesky output turns those into app.bsky.richtext.facet#link. The offsets are UTF-8 byte offsets, not character counts: a facet measured in characters slides off the URL as soon as any Japanese text precedes it, and underlines the wrong words. Scanning moved from preview.go to findLinks in main.go, which now reports every link with its offsets rather than just the first one; the preview card still goes to the first, which is the one a reader meets first. X is unaffected, as it linkifies URLs itself. The facet feature only gains its lexicon type when marshalled, so the test checks the encoded record rather than the struct. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
111 lines
3.2 KiB
Go
111 lines
3.2 KiB
Go
package main
|
|
|
|
import (
|
|
"net/url"
|
|
"testing"
|
|
)
|
|
|
|
func TestIsYouTube(t *testing.T) {
|
|
tests := []struct {
|
|
raw string
|
|
want bool
|
|
}{
|
|
{"https://www.youtube.com/watch?v=dQw4w9WgXcQ", true},
|
|
{"https://youtu.be/dQw4w9WgXcQ?t=42", true},
|
|
{"https://www.youtube.com/shorts/abc_123", true},
|
|
{"https://youtube.com/live/abc-123", true},
|
|
{"https://m.youtube.com/watch?v=abc&feature=share", true},
|
|
{"https://www.youtube.com/", false},
|
|
{"https://www.youtube.com/watch?list=PL123", false},
|
|
{"https://youtube.com.evil.example/watch?v=abc", false},
|
|
{"https://example.com/", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.raw, func(t *testing.T) {
|
|
u, err := url.Parse(tt.raw)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := isYouTube(u); got != tt.want {
|
|
t.Errorf("isYouTube(%q) = %v, want %v", tt.raw, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseMetaTags(t *testing.T) {
|
|
page := &fetched{
|
|
contentType: "text/html; charset=utf-8",
|
|
body: []byte(`<!doctype html><html><head>
|
|
<title>plain title</title>
|
|
<meta name="description" content="plain description">
|
|
<meta property="og:title" content="OGP タイトル">
|
|
<meta property="og:description" content="OGP の説明 & その続き" />
|
|
<meta content="https://example.com/card.png" property="og:image">
|
|
<meta property="og:title" content="a later duplicate">
|
|
</head><body>
|
|
<meta property="og:image" content="https://example.com/inbody.png">
|
|
</body></html>`),
|
|
}
|
|
|
|
tags, err := parseMetaTags(page)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
want := map[string]string{
|
|
"title": "plain title",
|
|
"description": "plain description",
|
|
"og:title": "OGP タイトル",
|
|
"og:description": "OGP の説明 & その続き",
|
|
"og:image": "https://example.com/card.png",
|
|
}
|
|
for key, value := range want {
|
|
if tags[key] != value {
|
|
t.Errorf("tags[%q] = %q, want %q", key, tags[key], value)
|
|
}
|
|
}
|
|
|
|
if got := tags.first("og:title", "twitter:title", "title"); got != "OGP タイトル" {
|
|
t.Errorf("first title = %q, want the OGP one", got)
|
|
}
|
|
if got := tags.first("twitter:title", "title"); got != "plain title" {
|
|
t.Errorf("first title = %q, want the fallback", got)
|
|
}
|
|
if got := tags.first("nothing:here"); got != "" {
|
|
t.Errorf("first of an absent key = %q, want empty", got)
|
|
}
|
|
}
|
|
|
|
func TestParseMetaTagsShiftJIS(t *testing.T) {
|
|
// "テスト" encoded as Shift_JIS, declared in the Content-Type header.
|
|
body := []byte(`<html><head><meta property="og:title" content="` +
|
|
"\x83e\x83X\x83g" + `"></head></html>`)
|
|
page := &fetched{contentType: "text/html; charset=Shift_JIS", body: body}
|
|
|
|
tags, err := parseMetaTags(page)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if tags["og:title"] != "テスト" {
|
|
t.Errorf("og:title = %q, want テスト", tags["og:title"])
|
|
}
|
|
}
|
|
|
|
func TestParseMetaTagsTruncated(t *testing.T) {
|
|
// A page cut off at maxHTMLBytes ends mid markup; what was read still counts.
|
|
page := &fetched{
|
|
contentType: "text/html",
|
|
body: []byte(`<html><head><meta property="og:title" content="kept"><meta property="og:desc`),
|
|
}
|
|
|
|
tags, err := parseMetaTags(page)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if tags["og:title"] != "kept" {
|
|
t.Errorf("og:title = %q, want kept", tags["og:title"])
|
|
}
|
|
}
|