go-jamming/app/webmention/recv/receive_test.go

423 lines
14 KiB
Go
Raw Normal View History

2021-04-09 18:04:04 +02:00
package recv
2021-04-08 12:16:33 +02:00
import (
"brainbaking.com/go-jamming/app/mf"
"brainbaking.com/go-jamming/app/notifier"
"brainbaking.com/go-jamming/db"
"encoding/json"
2021-04-08 12:16:33 +02:00
"errors"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
"io/ioutil"
"net/http"
"strings"
2021-04-08 16:37:04 +02:00
"testing"
"time"
"willnorris.com/go/microformats"
2021-04-08 12:16:33 +02:00
2021-04-09 18:04:04 +02:00
"brainbaking.com/go-jamming/common"
"brainbaking.com/go-jamming/mocks"
2021-04-08 12:16:33 +02:00
)
var conf = &common.Config{
AllowedWebmentionSources: []string{
2021-04-08 12:16:33 +02:00
"jefklakscodex.com",
"brainbaking.com",
2021-04-08 12:16:33 +02:00
},
Blacklist: []string{
"blacklisted.com",
},
Whitelist: []string{
"brainbaking.com",
"jefklakscodex.com",
},
2021-04-08 12:16:33 +02:00
}
func TestSaveAuthorPictureLocally(t *testing.T) {
cases := []struct {
label string
pictureUrl string
expectedPictureUrl string
expectedError error
}{
{
"Absolute URL gets 'downloaded' and replaced by relative",
"https://brainbaking.com/picture.jpg",
"/pictures/brainbaking.com",
nil,
},
{
"Refuses to download if it's from a silo domain and possibly involves GDPR privacy issues",
"https://brid.gy/picture.jpg",
"https://brid.gy/picture.jpg",
errWontDownloadBecauseOfPrivacy,
},
{
"Absolute URL does not get replaced but error if no valid image",
"https://brainbaking.com/index.xml",
"https://brainbaking.com/index.xml",
errPicNoRealImage,
},
{
"Absolute URL does not get replaced but error if download fails",
"https://brainbaking.com/thedogatemypic-nowitsmissing-shiii.png",
"https://brainbaking.com/thedogatemypic-nowitsmissing-shiii.png",
errPicUnableToDownload,
},
}
t.Cleanup(db.Purge)
for _, tc := range cases {
t.Run(tc.label, func(t *testing.T) {
repo := db.NewMentionRepo(conf)
recv := &Receiver{
Conf: conf,
Repo: repo,
RestClient: &mocks.RestClientMock{
GetBodyFunc: mocks.RelPathGetBodyFunc("../../../mocks/"),
},
}
indieweb := &mf.IndiewebData{
Source: tc.pictureUrl,
Author: mf.IndiewebAuthor{
Picture: tc.pictureUrl,
},
}
err := recv.saveAuthorPictureLocally(indieweb)
assert.Equal(t, tc.expectedPictureUrl, indieweb.Author.Picture)
assert.Equal(t, tc.expectedError, err)
})
}
}
2021-04-23 11:27:43 +02:00
func BenchmarkReceiveWithoutRestCalls(b *testing.B) {
origLog := zerolog.GlobalLevel()
zerolog.SetGlobalLevel(zerolog.Disabled)
defer zerolog.SetGlobalLevel(origLog)
wm := mf.Mention{
Source: "https://brainbaking.com/valid-indieweb-source.html",
Target: "https://jefklakscodex.com/articles",
}
data, err := ioutil.ReadFile("../../../mocks/valid-indieweb-source.html")
assert.NoError(b, err)
html := string(data)
repo := db.NewMentionRepo(conf)
recv := &Receiver{
Conf: conf,
Repo: repo,
RestClient: &mocks.RestClientMock{
GetBodyFunc: func(s string) (http.Header, string, error) {
return http.Header{}, html, nil
},
},
}
b.ReportAllocs()
for i := 0; i < b.N; i++ {
recv.Receive(wm)
}
}
func TestReceive(t *testing.T) {
cases := []struct {
label string
wm mf.Mention
2021-04-09 12:40:37 +02:00
json string
}{
{
label: "bugfix interface conversion panic unusual author part in mf",
wm: mf.Mention{
Source: "https://brainbaking.com/bugfix-interface-conversion-panic.html",
Target: "https://brainbaking.com/",
},
json: `{"author":{"name":"Ton Zijlstra","picture":"/pictures/brainbaking.com"},"name":"","content":"De allereerste Nederlandstalige meet-up van Obsidian.md gebruikers was interessant en leuk! We waren met zn vieren, Sebastiaan, Wouter, Frank en ik, en spraken bijna 2 uur met elkaar. Leuk om te vergelijken waarom en hoe we notities maken in Obsid...", "name":"Nabeschouwing: de eerste Nederlandstalige Obsidian meet-up", "published":"2021-04-25T11:24:48+02:00", "source":"https://brainbaking.com/bugfix-interface-conversion-panic.html", "target":"https://brainbaking.com/", "type":"mention", "url":"https://www.zylstra.org/blog/2021/04/nabeschouwing-de-eerste-nederlandstalige-obsidian-meet-up/"}`,
},
{
2021-04-09 12:40:37 +02:00
label: "receive a Webmention bookmark via twitter",
wm: mf.Mention{
2021-04-09 12:40:37 +02:00
Source: "https://brainbaking.com/valid-bridgy-twitter-source.html",
Target: "https://brainbaking.com/post/2021/03/the-indieweb-mixed-bag",
},
json: `{"author":{"name":"Jamie Tanna","picture":"/pictures/brainbaking.com"},"name":"","content":"Recommended read:\nThe IndieWeb Mixed Bag - Thoughts about the (d)evolution of blog interactions\nhttps://brainbaking.com/post/2021/03/the-indieweb-mixed-bag/","published":"2021-03-15T12:42:00+00:00","url":"https://brainbaking.com/mf2/2021/03/1bkre/","type":"bookmark","source":"https://brainbaking.com/valid-bridgy-twitter-source.html","target":"https://brainbaking.com/post/2021/03/the-indieweb-mixed-bag"}`,
},
{
2021-04-09 12:40:37 +02:00
label: "receive a brid.gy Webmention like",
wm: mf.Mention{
2021-04-09 12:40:37 +02:00
Source: "https://brainbaking.com/valid-bridgy-like.html",
// wrapped in a a class="u-like-of" tag
2021-04-09 12:40:37 +02:00
Target: "https://brainbaking.com/valid-indieweb-target.html",
},
// no dates in bridgy-to-mastodon likes...
json: `{"author":{"name":"Stampeding Longhorn","picture":"/pictures/brainbaking.com"},"name":"","content":"","published":"2020-01-01T12:30:00+00:00","url":"https://chat.brainbaking.com/notice/A4nx1rFwKUJYSe4TqK#favorited-by-A4nwg4LYyh4WgrJOXg","type":"like","source":"https://brainbaking.com/valid-bridgy-like.html","target":"https://brainbaking.com/valid-indieweb-target.html"}`,
},
{
2021-04-09 12:40:37 +02:00
label: "receive a brid.gy Webmention that has a url and photo without value",
wm: mf.Mention{
2021-04-09 12:40:37 +02:00
Source: "https://brainbaking.com/valid-bridgy-source.html",
Target: "https://brainbaking.com/valid-indieweb-target.html",
},
json: `{"author":{"name":"Stampeding Longhorn", "picture":"/pictures/brainbaking.com"}, "content":"@wouter The cat pictures are awesome. for jest tests!", "name":"@wouter The cat pictures are awesome. for jest tests!", "published":"2021-03-02T16:17:18+00:00", "source":"https://brainbaking.com/valid-bridgy-source.html", "target":"https://brainbaking.com/valid-indieweb-target.html", "type":"mention", "url":"https://social.linux.pizza/@StampedingLonghorn/105821099684887793"}`,
},
{
label: "receive saves a JSON file of indieweb-metadata if all is valid",
wm: mf.Mention{
2021-04-09 12:40:37 +02:00
Source: "https://brainbaking.com/valid-indieweb-source.html",
Target: "https://jefklakscodex.com/articles",
},
json: `{"author":{"name":"Wouter Groeneveld","picture":"/pictures/brainbaking.com"},"name":"I just learned about https://www.inklestudios.com/...","content":"This is cool, I just found out about valid indieweb target - so cool","published":"2021-03-06T12:41:00+00:00","url":"https://brainbaking.com/notes/2021/03/06h12m41s48/","type":"mention","source":"https://brainbaking.com/valid-indieweb-source.html","target":"https://jefklakscodex.com/articles"}`,
},
{
label: "receive saves a JSON file of indieweb-metadata with summary as content if present",
wm: mf.Mention{
2021-04-09 12:40:37 +02:00
Source: "https://brainbaking.com/valid-indieweb-source-with-summary.html",
Target: "https://brainbaking.com/valid-indieweb-target.html",
},
json: `{"author":{"name":"Wouter Groeneveld", "picture":"/pictures/brainbaking.com"}, "content":"This is cool, this is a summary!", "name":"I just learned about https://www.inklestudios.com/...", "published":"2021-03-06T12:41:00+00:00", "source":"https://brainbaking.com/valid-indieweb-source-with-summary.html", "target":"https://brainbaking.com/valid-indieweb-target.html", "type":"mention", "url":"https://brainbaking.com/notes/2021/03/06h12m41s48/"}`,
},
{
label: "receive saves a JSON file of non-indieweb-data such as title if all is valid",
wm: mf.Mention{
2021-04-09 12:40:37 +02:00
Source: "https://brainbaking.com/valid-nonindieweb-source.html",
Target: "https://brainbaking.com/valid-indieweb-target.html",
},
json: `{"author":{"name":"https://brainbaking.com/valid-nonindieweb-source.html", "picture":"/pictures/anonymous"}, "content":"Diablo 2 Twenty Years Later: A Retrospective | Jefklaks Codex", "name":"Diablo 2 Twenty Years Later: A Retrospective | Jefklaks Codex", "published":"2020-01-01T12:30:00+00:00", "source":"https://brainbaking.com/valid-nonindieweb-source.html", "target":"https://brainbaking.com/valid-indieweb-target.html", "type":"mention", "url":"https://brainbaking.com/valid-nonindieweb-source.html"}`,
2021-04-08 16:37:04 +02:00
},
}
for _, tc := range cases {
t.Run(tc.label, func(t *testing.T) {
common.Now = func() time.Time {
return time.Date(2020, time.January, 1, 12, 30, 0, 0, time.UTC)
}
repo := db.NewMentionRepo(conf)
2021-04-09 12:40:37 +02:00
receiver := &Receiver{
Conf: conf,
Repo: repo,
2021-04-09 12:40:37 +02:00
RestClient: &mocks.RestClientMock{
GetBodyFunc: mocks.RelPathGetBodyFunc("../../../mocks/"),
},
}
2021-04-09 12:40:37 +02:00
receiver.Receive(tc.wm)
actual := repo.Get(tc.wm)
actualJson, _ := json.Marshal(actual)
assert.JSONEq(t, tc.json, string(actualJson))
})
}
2021-04-08 16:37:04 +02:00
}
2021-04-08 12:16:33 +02:00
func TestReceiveTargetDoesNotExistAnymoreDeletesPossiblyOlderWebmention(t *testing.T) {
repo := db.NewMentionRepo(conf)
t.Cleanup(db.Purge)
2021-04-08 12:16:33 +02:00
wm := mf.Mention{
2021-04-09 12:40:37 +02:00
Source: "https://brainbaking.com",
Target: "https://jefklakscodex.com/articles",
2021-04-08 12:16:33 +02:00
}
repo.Save(wm, &mf.IndiewebData{
Name: "something something",
})
2021-04-08 12:16:33 +02:00
client := &mocks.RestClientMock{
GetBodyFunc: func(url string) (http.Header, string, error) {
return nil, "", errors.New("whoops")
2021-04-08 12:16:33 +02:00
},
}
2021-04-09 12:40:37 +02:00
receiver := &Receiver{
Conf: conf,
2021-04-09 12:40:37 +02:00
RestClient: client,
Repo: repo,
2021-04-08 12:16:33 +02:00
}
2021-04-09 12:40:37 +02:00
receiver.Receive(wm)
indb := repo.Get(wm)
assert.Empty(t, indb)
}
func TestReceiveFromNotInWhitelistSavesInModerationAndNotifies(t *testing.T) {
wm := mf.Mention{
Source: "https://brainbaking.com/valid-indieweb-source.html",
Target: "https://brainbaking.com/valid-indieweb-target.html",
}
cnf := &common.Config{
AllowedWebmentionSources: []string{
"brainbaking.com",
},
BaseURL: "https://jam.brainbaking.com/",
Token: "mytoken",
Blacklist: []string{},
Whitelist: []string{},
}
repo := db.NewMentionRepo(cnf)
t.Cleanup(db.Purge)
notifierMock := &notifier.StringNotifier{
Conf: cnf,
Output: "",
}
receiver := &Receiver{
Conf: cnf,
Repo: repo,
RestClient: &mocks.RestClientMock{
GetBodyFunc: mocks.RelPathGetBodyFunc("../../../mocks/"),
},
Notifier: notifierMock,
}
receiver.Receive(wm)
assert.Empty(t, repo.GetAll("brainbaking.com").Data)
assert.Equal(t, 1, len(repo.GetAllToModerate("brainbaking.com").Data))
assert.Contains(t, notifierMock.Output, "in moderation!")
}
func TestReceiveFromBlacklistedDomainDoesNothing(t *testing.T) {
wm := mf.Mention{
Source: "https://blacklisted.com/whoops",
Target: "https://brainbaking.com/valid-indieweb-source.html",
}
repo := db.NewMentionRepo(conf)
t.Cleanup(db.Purge)
receiver := &Receiver{
Conf: conf,
Repo: repo,
}
receiver.Receive(wm)
assert.Empty(t, repo.GetAll("brainbaking.com").Data)
assert.Empty(t, repo.GetAllToModerate("brainbaking.com").Data)
}
func TestReceiveTargetThatDoesNotPointToTheSourceDoesNothing(t *testing.T) {
wm := mf.Mention{
2021-04-09 12:40:37 +02:00
Source: "https://brainbaking.com/valid-indieweb-source.html",
Target: "https://brainbaking.com/valid-indieweb-source.html",
}
repo := db.NewMentionRepo(conf)
t.Cleanup(db.Purge)
2021-04-09 12:40:37 +02:00
receiver := &Receiver{
Conf: conf,
Repo: repo,
2021-04-09 12:40:37 +02:00
RestClient: &mocks.RestClientMock{
GetBodyFunc: mocks.RelPathGetBodyFunc("../../../mocks/"),
},
}
2021-04-09 12:40:37 +02:00
receiver.Receive(wm)
assert.Empty(t, repo.GetAll("brainbaking.com").Data)
assert.Empty(t, repo.GetAllToModerate("brainbaking.com").Data)
2021-04-08 12:50:15 +02:00
}
func TestConvertBodyToIndiewebDataWithComplicatedData(t *testing.T) {
cases := []struct {
source string
target string
mockFile string
expectedAuthorName string
}{
{
"https://pauho.net/2022/05/04/662325/",
"https://brainbaking.com/post/2022/04/cool-things-people-do-with-their-blogs/",
"../../../mocks/indieweb-complicated-data.html",
"Paul Houlihan",
},
{
"https://ruk.ca/favourite/drought",
"https://brainbaking.com/post/2022/05/drought/",
"../../../mocks/indieweb-hcard-next-to-name.html",
"Peter Rukavina",
},
}
cnf := &common.Config{
AllowedWebmentionSources: []string{
"brainbaking.com",
},
}
recv := &Receiver{
Conf: cnf,
}
for _, tc := range cases {
t.Run("indieweb author "+tc.expectedAuthorName, func(t *testing.T) {
wm := mf.Mention{
Source: tc.source,
Target: tc.target,
}
src, err := ioutil.ReadFile(tc.mockFile)
assert.NoError(t, err)
body := string(src)
data := microformats.Parse(strings.NewReader(body), wm.SourceUrl())
indieweb := recv.convertBodyToIndiewebData(body, wm, data)
assert.Equal(t, tc.expectedAuthorName, indieweb.Author.Name)
})
}
}
func TestProcessSourceBodyAnonymizesBothAuthorPictureAndNameIfComingFromSilo(t *testing.T) {
wm := mf.Mention{
Source: "https://brid.gy/post/twitter/ChrisAldrich/1387130900962443264",
Target: "https://brainbaking.com/",
}
cnf := &common.Config{
AllowedWebmentionSources: []string{
"brainbaking.com",
},
Whitelist: []string{
"brid.gy",
},
}
repo := db.NewMentionRepo(cnf)
t.Cleanup(db.Purge)
recv := &Receiver{
Conf: cnf,
Repo: repo,
}
src, err := ioutil.ReadFile("../../../mocks/valid-bridgy-source.html")
assert.NoError(t, err)
recv.processSourceBody(string(src), wm)
savedMention := repo.Get(wm)
assert.Equal(t, "Anonymous", savedMention.Author.Name)
assert.Equal(t, "/pictures/anonymous", savedMention.Author.Picture)
}
2021-04-08 12:50:15 +02:00
func TestProcessSourceBodyAbortsIfNoMentionOfTargetFoundInSourceHtml(t *testing.T) {
wm := mf.Mention{
2021-04-09 12:40:37 +02:00
Source: "https://brainbaking.com",
Target: "https://jefklakscodex.com/articles",
2021-04-08 12:50:15 +02:00
}
repo := db.NewMentionRepo(conf)
t.Cleanup(db.Purge)
recv := &Receiver{
Conf: conf,
Repo: repo,
2021-04-08 12:50:15 +02:00
}
recv.processSourceBody("<html>my nice body</html>", wm)
assert.Empty(t, repo.Get(wm))
2021-04-08 12:50:15 +02:00
}
func TestProcessAuthorPictureAnonymizesIfEmpty(t *testing.T) {
recv := &Receiver{}
indieweb := &mf.IndiewebData{
Author: mf.IndiewebAuthor{
Picture: "",
},
}
recv.ProcessAuthorPicture(indieweb)
assert.Equal(t, "/pictures/anonymous", indieweb.Author.Picture)
}