Compare commits
14 Commits
0b3ae3bea1
...
4aaa14e30c
Author | SHA1 | Date |
---|---|---|
Niko Abeler | 4aaa14e30c | |
Niko Abeler | 8d1dacc1f5 | |
Niko Abeler | 73ec606b95 | |
Niko Abeler | a5250eb01c | |
Niko Abeler | 61a5435971 | |
Niko Abeler | 2309dfb0ce | |
Niko Abeler | 01593c0a36 | |
Niko Abeler | 3bd168de00 | |
Niko Abeler | b6e996c3d9 | |
Niko Abeler | 9d4b96fcb6 | |
Niko Abeler | 1ff5805e18 | |
Niko Abeler | cf367b6abd | |
Niko Abeler | 76633aaf89 | |
Niko Abeler | 691158cd0e |
|
@ -26,6 +26,9 @@ Each directory in the `/users/` directory of a repository is considered a user.
|
|||
\- media/
|
||||
-- Contains all media files used in the blog post.
|
||||
-- All files in this folder will be publicly available
|
||||
\- webmention/
|
||||
\- <hash>.yml
|
||||
-- Contains data for a received webmention
|
||||
\- meta/
|
||||
\- base.html
|
||||
-- The template used to render all sites
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
@ -56,6 +57,77 @@ func userIndexHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Requ
|
|||
}
|
||||
}
|
||||
|
||||
func userWebmentionHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Request, httprouter.Params) {
|
||||
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
user, err := getUserFromRepo(repo, ps)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("User not found"))
|
||||
return
|
||||
}
|
||||
err = r.ParseForm()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("Unable to parse form data"))
|
||||
return
|
||||
}
|
||||
params := r.PostForm
|
||||
target := params["target"]
|
||||
source := params["source"]
|
||||
if len(target) == 0 {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("No target provided"))
|
||||
return
|
||||
}
|
||||
if len(source) == 0 {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("No source provided"))
|
||||
return
|
||||
}
|
||||
|
||||
if len(target[0]) < 7 || (target[0][:7] != "http://" && target[0][:8] != "https://") {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("Not a valid target"))
|
||||
return
|
||||
}
|
||||
|
||||
if len(source[0]) < 7 || (source[0][:7] != "http://" && source[0][:8] != "https://") {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("Not a valid source"))
|
||||
return
|
||||
}
|
||||
|
||||
if source[0] == target[0] {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("target and source are equal"))
|
||||
return
|
||||
}
|
||||
|
||||
parts := strings.Split(target[0], "/")
|
||||
if len(parts) < 2 {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("Not found"))
|
||||
return
|
||||
}
|
||||
postId := parts[len(parts)-2]
|
||||
post, err := user.GetPost(postId)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("Post not found"))
|
||||
return
|
||||
}
|
||||
err = post.AddWebmention(source[0])
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("Unable to process webmention"))
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
w.Write([]byte(""))
|
||||
}
|
||||
}
|
||||
|
||||
func userRSSHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Request, httprouter.Params) {
|
||||
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
user, err := getUserFromRepo(repo, ps)
|
||||
|
|
|
@ -14,6 +14,7 @@ func Router(repo *owl.Repository) http.Handler {
|
|||
router.ServeFiles("/static/*filepath", http.Dir(repo.StaticDir()))
|
||||
router.GET("/", repoIndexHandler(repo))
|
||||
router.GET("/user/:user/", userIndexHandler(repo))
|
||||
router.POST("/user/:user/webmention/", userWebmentionHandler(repo))
|
||||
router.GET("/user/:user/index.xml", userRSSHandler(repo))
|
||||
router.GET("/user/:user/posts/:post/", postHandler(repo))
|
||||
router.GET("/user/:user/posts/:post/media/*filepath", postMediaHandler(repo))
|
||||
|
@ -25,6 +26,7 @@ func SingleUserRouter(repo *owl.Repository) http.Handler {
|
|||
router := httprouter.New()
|
||||
router.ServeFiles("/static/*filepath", http.Dir(repo.StaticDir()))
|
||||
router.GET("/", userIndexHandler(repo))
|
||||
router.POST("/webmention/", userWebmentionHandler(repo))
|
||||
router.GET("/index.xml", userRSSHandler(repo))
|
||||
router.GET("/posts/:post/", postHandler(repo))
|
||||
router.GET("/posts/:post/media/*filepath", postMediaHandler(repo))
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
package main_test
|
||||
|
||||
import (
|
||||
"h4kor/owl-blogs"
|
||||
main "h4kor/owl-blogs/cmd/owl-web"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func setupWebmentionTest(repo owl.Repository, user owl.User, target string, source string) (*httptest.ResponseRecorder, error) {
|
||||
|
||||
data := url.Values{}
|
||||
data.Set("target", target)
|
||||
data.Set("source", source)
|
||||
|
||||
// Create Request and Response
|
||||
req, err := http.NewRequest("POST", user.UrlPath()+"webmention/", strings.NewReader(data.Encode()))
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Add("Content-Length", strconv.Itoa(len(data.Encode())))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
router := main.Router(&repo)
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
return rr, nil
|
||||
}
|
||||
|
||||
func assertStatus(t *testing.T, rr *httptest.ResponseRecorder, expStatus int) {
|
||||
if status := rr.Code; status != expStatus {
|
||||
t.Errorf("handler returned wrong status code: got %v want %v",
|
||||
status, expStatus)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebmentionHandleAccepts(t *testing.T) {
|
||||
repo := getTestRepo()
|
||||
user, _ := repo.CreateUser("test-1")
|
||||
post, _ := user.CreateNewPost("post-1")
|
||||
|
||||
target := post.FullUrl()
|
||||
source := "https://example.com"
|
||||
|
||||
rr, err := setupWebmentionTest(repo, user, target, source)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertStatus(t, rr, http.StatusAccepted)
|
||||
|
||||
}
|
||||
|
||||
func TestWebmentionWrittenToPost(t *testing.T) {
|
||||
|
||||
repo := getTestRepo()
|
||||
user, _ := repo.CreateUser("test-1")
|
||||
post, _ := user.CreateNewPost("post-1")
|
||||
|
||||
target := post.FullUrl()
|
||||
source := "https://example.com"
|
||||
|
||||
rr, err := setupWebmentionTest(repo, user, target, source)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Check the status code is what we expect.
|
||||
assertStatus(t, rr, http.StatusAccepted)
|
||||
|
||||
if len(post.Webmentions()) != 1 {
|
||||
t.Errorf("no webmention written to post")
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// https://www.w3.org/TR/webmention/#h-request-verification
|
||||
//
|
||||
|
||||
// The receiver MUST check that source and target are valid URLs [URL]
|
||||
// and are of schemes that are supported by the receiver.
|
||||
// (Most commonly this means checking that the source and target schemes are http or https).
|
||||
func TestWebmentionSourceValidation(t *testing.T) {
|
||||
|
||||
repo := getTestRepo()
|
||||
user, _ := repo.CreateUser("test-1")
|
||||
post, _ := user.CreateNewPost("post-1")
|
||||
|
||||
target := post.FullUrl()
|
||||
source := "ftp://example.com"
|
||||
|
||||
rr, err := setupWebmentionTest(repo, user, target, source)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertStatus(t, rr, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func TestWebmentionTargetValidation(t *testing.T) {
|
||||
|
||||
repo := getTestRepo()
|
||||
user, _ := repo.CreateUser("test-1")
|
||||
post, _ := user.CreateNewPost("post-1")
|
||||
|
||||
target := "ftp://example.com"
|
||||
source := post.FullUrl()
|
||||
|
||||
rr, err := setupWebmentionTest(repo, user, target, source)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertStatus(t, rr, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
// The receiver MUST reject the request if the source URL is the same as the target URL.
|
||||
|
||||
func TestWebmentionSameTargetAndSource(t *testing.T) {
|
||||
|
||||
repo := getTestRepo()
|
||||
user, _ := repo.CreateUser("test-1")
|
||||
post, _ := user.CreateNewPost("post-1")
|
||||
|
||||
target := post.FullUrl()
|
||||
source := post.FullUrl()
|
||||
|
||||
rr, err := setupWebmentionTest(repo, user, target, source)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertStatus(t, rr, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
// The receiver SHOULD check that target is a valid resource for which it can accept Webmentions.
|
||||
// This check SHOULD happen synchronously to reject invalid Webmentions before more in-depth verification begins.
|
||||
// What a "valid resource" means is up to the receiver.
|
||||
func TestValidationOfTarget(t *testing.T) {
|
||||
repo := getTestRepo()
|
||||
user, _ := repo.CreateUser("test-1")
|
||||
post, _ := user.CreateNewPost("post-1")
|
||||
|
||||
target := post.FullUrl()
|
||||
target = target[:len(target)-1] + "invalid"
|
||||
source := post.FullUrl()
|
||||
|
||||
rr, err := setupWebmentionTest(repo, user, target, source)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assertStatus(t, rr, http.StatusBadRequest)
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ .Title }}</title>
|
||||
<link rel="stylesheet" href="/static/pico.min.css">
|
||||
<link rel="webmention" href="{{ .User.WebmentionUrl }}">
|
||||
<style>
|
||||
header {
|
||||
background-color: {{.HeaderColor}};
|
||||
|
|
|
@ -13,4 +13,24 @@
|
|||
<div class="e-content">
|
||||
{{.Content}}
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
{{if .Post.ApprovedWebmentions}}
|
||||
<h3>
|
||||
Webmentions
|
||||
</h3>
|
||||
<ul>
|
||||
{{range .Post.ApprovedWebmentions}}
|
||||
<li>
|
||||
<a href="{{.Source}}">
|
||||
{{if .Title}}
|
||||
{{.Title}}
|
||||
{{else}}
|
||||
{{.Source}}
|
||||
{{end}}
|
||||
</a>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{end}}
|
||||
</div>
|
1
go.mod
1
go.mod
|
@ -6,5 +6,6 @@ require (
|
|||
github.com/julienschmidt/httprouter v1.3.0 // indirect
|
||||
github.com/yuin/goldmark v1.4.13 // indirect
|
||||
github.com/yuin/goldmark-meta v1.1.0 // indirect
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b // indirect
|
||||
gopkg.in/yaml.v2 v2.3.0 // indirect
|
||||
)
|
||||
|
|
2
go.sum
2
go.sum
|
@ -4,6 +4,8 @@ github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
|
|||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc=
|
||||
github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0=
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWIEjGcGAkacif7oYQaUY=
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
|
12
owl_test.go
12
owl_test.go
|
@ -6,6 +6,18 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
type MockMicroformatParser struct{}
|
||||
|
||||
func (*MockMicroformatParser) ParseHEntry(data []byte) (owl.ParsedHEntry, error) {
|
||||
return owl.ParsedHEntry{Title: "Mock Title"}, nil
|
||||
}
|
||||
|
||||
type MockHttpRetriever struct{}
|
||||
|
||||
func (*MockHttpRetriever) Get(url string) ([]byte, error) {
|
||||
return []byte(""), nil
|
||||
}
|
||||
|
||||
func randomName() string {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
var letters = []rune("abcdefghijklmnopqrstuvwxyz")
|
||||
|
|
120
post.go
120
post.go
|
@ -2,8 +2,13 @@ package owl
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
|
||||
"github.com/yuin/goldmark"
|
||||
"github.com/yuin/goldmark/extension"
|
||||
|
@ -39,6 +44,10 @@ func (post Post) MediaDir() string {
|
|||
return path.Join(post.Dir(), "media")
|
||||
}
|
||||
|
||||
func (post Post) WebmentionDir() string {
|
||||
return path.Join(post.Dir(), "webmention")
|
||||
}
|
||||
|
||||
func (post Post) UrlPath() string {
|
||||
return post.user.UrlPath() + "posts/" + post.id + "/"
|
||||
}
|
||||
|
@ -139,3 +148,114 @@ func (post *Post) LoadMeta() error {
|
|||
post.meta = meta
|
||||
return nil
|
||||
}
|
||||
|
||||
func (post *Post) WebmentionFile(source string) string {
|
||||
|
||||
hash := sha256.Sum256([]byte(source))
|
||||
hashStr := base64.URLEncoding.EncodeToString(hash[:])
|
||||
return path.Join(post.WebmentionDir(), hashStr+".yml")
|
||||
}
|
||||
|
||||
func (post *Post) PersistWebmention(webmention Webmention) error {
|
||||
// ensure dir exists
|
||||
os.MkdirAll(post.WebmentionDir(), 0755)
|
||||
|
||||
// write to file
|
||||
fileName := post.WebmentionFile(webmention.Source)
|
||||
data, err := yaml.Marshal(webmention)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.WriteFile(fileName, data, 0644)
|
||||
}
|
||||
|
||||
func (post *Post) Webmention(source string) (Webmention, error) {
|
||||
// ensure dir exists
|
||||
os.MkdirAll(post.WebmentionDir(), 0755)
|
||||
|
||||
// Check if file exists
|
||||
fileName := post.WebmentionFile(source)
|
||||
if !fileExists(fileName) {
|
||||
// return error if file doesn't exist
|
||||
return Webmention{}, fmt.Errorf("Webmention file not found: %s", source)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return Webmention{}, err
|
||||
}
|
||||
|
||||
mention := Webmention{}
|
||||
err = yaml.Unmarshal(data, &mention)
|
||||
if err != nil {
|
||||
return Webmention{}, err
|
||||
}
|
||||
|
||||
return mention, nil
|
||||
}
|
||||
|
||||
func (post *Post) AddWebmention(source string) error {
|
||||
// Check if file already exists
|
||||
_, err := post.Webmention(source)
|
||||
if err != nil {
|
||||
webmention := Webmention{
|
||||
Source: source,
|
||||
}
|
||||
defer post.EnrichWebmention(source)
|
||||
return post.PersistWebmention(webmention)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (post *Post) EnrichWebmention(source string) error {
|
||||
html, err := post.user.repo.Retriever.Get(source)
|
||||
if err == nil {
|
||||
webmention, err := post.Webmention(source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
entry, err := post.user.repo.Parser.ParseHEntry(html)
|
||||
if err == nil {
|
||||
webmention.Title = entry.Title
|
||||
return post.PersistWebmention(webmention)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (post *Post) Webmentions() []Webmention {
|
||||
// ensure dir exists
|
||||
os.MkdirAll(post.WebmentionDir(), 0755)
|
||||
files := listDir(post.WebmentionDir())
|
||||
webmentions := []Webmention{}
|
||||
for _, file := range files {
|
||||
data, err := os.ReadFile(path.Join(post.WebmentionDir(), file))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
mention := Webmention{}
|
||||
err = yaml.Unmarshal(data, &mention)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
webmentions = append(webmentions, mention)
|
||||
}
|
||||
|
||||
return webmentions
|
||||
}
|
||||
|
||||
func (post *Post) ApprovedWebmentions() []Webmention {
|
||||
webmentions := post.Webmentions()
|
||||
approved := []Webmention{}
|
||||
for _, webmention := range webmentions {
|
||||
if webmention.ApprovalStatus == "approved" {
|
||||
approved = append(approved, webmention)
|
||||
}
|
||||
}
|
||||
|
||||
// sort by retrieved date
|
||||
sort.Slice(approved, func(i, j int) bool {
|
||||
return approved[i].RetrievedAt.After(approved[j].RetrievedAt)
|
||||
})
|
||||
return approved
|
||||
}
|
||||
|
|
140
post_test.go
140
post_test.go
|
@ -1,10 +1,12 @@
|
|||
package owl_test
|
||||
|
||||
import (
|
||||
"h4kor/owl-blogs"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestCanGetPostTitle(t *testing.T) {
|
||||
|
@ -161,5 +163,143 @@ func TestLoadMeta(t *testing.T) {
|
|||
if post.Meta().Draft != true {
|
||||
t.Errorf("Expected title: %v, got %v", true, post.Meta().Draft)
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Webmention
|
||||
///
|
||||
|
||||
func TestPersistWebmention(t *testing.T) {
|
||||
repo := getTestRepo()
|
||||
user, _ := repo.CreateUser("testuser")
|
||||
post, _ := user.CreateNewPost("testpost")
|
||||
webmention := owl.Webmention{
|
||||
Source: "http://example.com/source",
|
||||
}
|
||||
err := post.PersistWebmention(webmention)
|
||||
if err != nil {
|
||||
t.Errorf("Got error: %v", err)
|
||||
}
|
||||
mentions := post.Webmentions()
|
||||
if len(mentions) != 1 {
|
||||
t.Errorf("Expected 1 webmention, got %d", len(mentions))
|
||||
}
|
||||
|
||||
if mentions[0].Source != webmention.Source {
|
||||
t.Errorf("Expected source: %s, got %s", webmention.Source, mentions[0].Source)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddWebmentionCreatesFile(t *testing.T) {
|
||||
repo := getTestRepo()
|
||||
repo.Retriever = &MockHttpRetriever{}
|
||||
repo.Parser = &MockMicroformatParser{}
|
||||
user, _ := repo.CreateUser("testuser")
|
||||
post, _ := user.CreateNewPost("testpost")
|
||||
|
||||
err := post.AddWebmention("https://example.com")
|
||||
if err != nil {
|
||||
t.Errorf("Got Error: %v", err)
|
||||
}
|
||||
|
||||
mentions := post.Webmentions()
|
||||
if len(mentions) != 1 {
|
||||
t.Errorf("Expected 1 webmention, got %d", len(mentions))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddWebmentionNotOverwritingFile(t *testing.T) {
|
||||
repo := getTestRepo()
|
||||
repo.Retriever = &MockHttpRetriever{}
|
||||
repo.Parser = &MockMicroformatParser{}
|
||||
user, _ := repo.CreateUser("testuser")
|
||||
post, _ := user.CreateNewPost("testpost")
|
||||
|
||||
post.AddWebmention("https://example.com")
|
||||
dir, _ := os.Open(post.WebmentionDir())
|
||||
defer dir.Close()
|
||||
files, _ := dir.Readdirnames(-1)
|
||||
|
||||
if len(files) != 1 {
|
||||
t.Error("No file created for webmention")
|
||||
}
|
||||
|
||||
content := "url: https://example.com\n"
|
||||
content += "verified: true"
|
||||
os.WriteFile(path.Join(post.WebmentionDir(), files[0]), []byte(content), 0644)
|
||||
|
||||
post.AddWebmention("https://example.com")
|
||||
|
||||
fileContent, _ := os.ReadFile(path.Join(post.WebmentionDir(), files[0]))
|
||||
if string(fileContent) != content {
|
||||
t.Error("File content was modified.")
|
||||
t.Errorf("Got: %v", fileContent)
|
||||
t.Errorf("Expected: %v", content)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddWebmentionAddsParsedTitle(t *testing.T) {
|
||||
repo := getTestRepo()
|
||||
repo.Retriever = &MockHttpRetriever{}
|
||||
repo.Parser = &MockMicroformatParser{}
|
||||
user, _ := repo.CreateUser("testuser")
|
||||
post, _ := user.CreateNewPost("testpost")
|
||||
|
||||
post.AddWebmention("https://example.com")
|
||||
dir, _ := os.Open(post.WebmentionDir())
|
||||
defer dir.Close()
|
||||
files, _ := dir.Readdirnames(-1)
|
||||
|
||||
if len(files) != 1 {
|
||||
t.Error("No file created for webmention")
|
||||
}
|
||||
|
||||
fileContent, _ := os.ReadFile(path.Join(post.WebmentionDir(), files[0]))
|
||||
if !strings.Contains(string(fileContent), "Mock Title") {
|
||||
t.Error("File not containing the title.")
|
||||
t.Errorf("Got: %v", string(fileContent))
|
||||
}
|
||||
}
|
||||
|
||||
func TestApprovedWebmentions(t *testing.T) {
|
||||
repo := getTestRepo()
|
||||
user, _ := repo.CreateUser("testuser")
|
||||
post, _ := user.CreateNewPost("testpost")
|
||||
webmention := owl.Webmention{
|
||||
Source: "http://example.com/source",
|
||||
ApprovalStatus: "approved",
|
||||
RetrievedAt: time.Now(),
|
||||
}
|
||||
post.PersistWebmention(webmention)
|
||||
webmention = owl.Webmention{
|
||||
Source: "http://example.com/source2",
|
||||
ApprovalStatus: "",
|
||||
RetrievedAt: time.Now().Add(time.Hour * -1),
|
||||
}
|
||||
post.PersistWebmention(webmention)
|
||||
webmention = owl.Webmention{
|
||||
Source: "http://example.com/source3",
|
||||
ApprovalStatus: "approved",
|
||||
RetrievedAt: time.Now().Add(time.Hour * -2),
|
||||
}
|
||||
post.PersistWebmention(webmention)
|
||||
webmention = owl.Webmention{
|
||||
Source: "http://example.com/source4",
|
||||
ApprovalStatus: "rejected",
|
||||
RetrievedAt: time.Now().Add(time.Hour * -3),
|
||||
}
|
||||
post.PersistWebmention(webmention)
|
||||
|
||||
webmentions := post.ApprovedWebmentions()
|
||||
if len(webmentions) != 2 {
|
||||
t.Errorf("Expected 2 webmentions, got %d", len(webmentions))
|
||||
}
|
||||
|
||||
if webmentions[0].Source != "http://example.com/source" {
|
||||
t.Errorf("Expected source: %s, got %s", "http://example.com/source", webmentions[0].Source)
|
||||
}
|
||||
if webmentions[1].Source != "http://example.com/source3" {
|
||||
t.Errorf("Expected source: %s, got %s", "http://example.com/source3", webmentions[1].Source)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
docker build . -t git.libove.org/h4kor/owl-blogs
|
||||
docker push git.libove.org/h4kor/owl-blogs
|
||||
docker build . -t git.libove.org/h4kor/owl-blogs:$1
|
||||
docker push git.libove.org/h4kor/owl-blogs:$1
|
|
@ -6,6 +6,7 @@ import (
|
|||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestCanRenderPost(t *testing.T) {
|
||||
|
@ -143,3 +144,58 @@ func TestRendersHeaderTitle(t *testing.T) {
|
|||
t.Error("Header color not rendered. Got: " + result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderPostIncludesRelToWebMention(t *testing.T) {
|
||||
user := getTestUser()
|
||||
post, _ := user.CreateNewPost("testpost")
|
||||
|
||||
result, _ := owl.RenderPost(&post)
|
||||
if !strings.Contains(result, "rel=\"webmention\"") {
|
||||
t.Error("webmention rel not rendered. Got: " + result)
|
||||
}
|
||||
|
||||
if !strings.Contains(result, "href=\""+user.WebmentionUrl()+"\"") {
|
||||
t.Error("webmention href not rendered. Got: " + result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderPostAddsLinksToApprovedWebmention(t *testing.T) {
|
||||
user := getTestUser()
|
||||
post, _ := user.CreateNewPost("testpost")
|
||||
webmention := owl.Webmention{
|
||||
Source: "http://example.com/source3",
|
||||
Title: "Test Title",
|
||||
ApprovalStatus: "approved",
|
||||
RetrievedAt: time.Now().Add(time.Hour * -2),
|
||||
}
|
||||
post.PersistWebmention(webmention)
|
||||
webmention = owl.Webmention{
|
||||
Source: "http://example.com/source4",
|
||||
ApprovalStatus: "rejected",
|
||||
RetrievedAt: time.Now().Add(time.Hour * -3),
|
||||
}
|
||||
post.PersistWebmention(webmention)
|
||||
|
||||
result, _ := owl.RenderPost(&post)
|
||||
if !strings.Contains(result, "http://example.com/source3") {
|
||||
t.Error("webmention not rendered. Got: " + result)
|
||||
}
|
||||
if !strings.Contains(result, "Test Title") {
|
||||
t.Error("webmention title not rendered. Got: " + result)
|
||||
}
|
||||
if strings.Contains(result, "http://example.com/source4") {
|
||||
t.Error("unapproved webmention rendered. Got: " + result)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestRenderPostNotMentioningWebmentionsIfNoAvail(t *testing.T) {
|
||||
user := getTestUser()
|
||||
post, _ := user.CreateNewPost("testpost")
|
||||
result, _ := owl.RenderPost(&post)
|
||||
|
||||
if strings.Contains(result, "Webmention") {
|
||||
t.Error("Webmention mentioned. Got: " + result)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ type Repository struct {
|
|||
single_user_mode bool
|
||||
active_user string
|
||||
allow_raw_html bool
|
||||
Retriever HttpRetriever
|
||||
Parser MicroformatParser
|
||||
}
|
||||
|
||||
type RepoConfig struct {
|
||||
|
@ -27,7 +29,7 @@ type RepoConfig struct {
|
|||
}
|
||||
|
||||
func CreateRepository(name string) (Repository, error) {
|
||||
newRepo := Repository{name: name}
|
||||
newRepo := Repository{name: name, Parser: OwlMicroformatParser{}, Retriever: OwlHttpRetriever{}}
|
||||
// check if repository already exists
|
||||
if dirExists(newRepo.Dir()) {
|
||||
return Repository{}, fmt.Errorf("Repository already exists")
|
||||
|
@ -61,7 +63,7 @@ func CreateRepository(name string) (Repository, error) {
|
|||
|
||||
func OpenRepository(name string) (Repository, error) {
|
||||
|
||||
repo := Repository{name: name}
|
||||
repo := Repository{name: name, Parser: OwlMicroformatParser{}, Retriever: OwlHttpRetriever{}}
|
||||
if !dirExists(repo.Dir()) {
|
||||
return Repository{}, fmt.Errorf("Repository does not exist: " + repo.Dir())
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ func TestCannotCreateExistingRepository(t *testing.T) {
|
|||
|
||||
func TestCanCreateANewUser(t *testing.T) {
|
||||
// Create a new user
|
||||
repo, _ := owl.CreateRepository(testRepoName())
|
||||
repo := getTestRepo()
|
||||
user, _ := repo.CreateUser(randomUserName())
|
||||
if _, err := os.Stat(path.Join(user.Dir(), "")); err != nil {
|
||||
t.Error("User directory not created")
|
||||
|
@ -36,7 +36,7 @@ func TestCanCreateANewUser(t *testing.T) {
|
|||
|
||||
func TestCannotRecreateExisitingUser(t *testing.T) {
|
||||
// Create a new user
|
||||
repo, _ := owl.CreateRepository(testRepoName())
|
||||
repo := getTestRepo()
|
||||
userName := randomUserName()
|
||||
repo.CreateUser(userName)
|
||||
_, err := repo.CreateUser(userName)
|
||||
|
@ -47,7 +47,7 @@ func TestCannotRecreateExisitingUser(t *testing.T) {
|
|||
|
||||
func TestCreateUserAddsVersionFile(t *testing.T) {
|
||||
// Create a new user
|
||||
repo, _ := owl.CreateRepository(testRepoName())
|
||||
repo := getTestRepo()
|
||||
user, _ := repo.CreateUser(randomUserName())
|
||||
if _, err := os.Stat(path.Join(user.Dir(), "/meta/VERSION")); err != nil {
|
||||
t.Error("Version file not created")
|
||||
|
@ -56,7 +56,7 @@ func TestCreateUserAddsVersionFile(t *testing.T) {
|
|||
|
||||
func TestCreateUserAddsBaseHtmlFile(t *testing.T) {
|
||||
// Create a new user
|
||||
repo, _ := owl.CreateRepository(testRepoName())
|
||||
repo := getTestRepo()
|
||||
user, _ := repo.CreateUser(randomUserName())
|
||||
if _, err := os.Stat(path.Join(user.Dir(), "/meta/base.html")); err != nil {
|
||||
t.Error("Base html file not created")
|
||||
|
@ -65,7 +65,7 @@ func TestCreateUserAddsBaseHtmlFile(t *testing.T) {
|
|||
|
||||
func TestCreateUserAddConfigYml(t *testing.T) {
|
||||
// Create a new user
|
||||
repo, _ := owl.CreateRepository(testRepoName())
|
||||
repo := getTestRepo()
|
||||
user, _ := repo.CreateUser(randomUserName())
|
||||
if _, err := os.Stat(path.Join(user.Dir(), "/meta/config.yml")); err != nil {
|
||||
t.Error("Config file not created")
|
||||
|
@ -74,7 +74,7 @@ func TestCreateUserAddConfigYml(t *testing.T) {
|
|||
|
||||
func TestCreateUserAddsPublicFolder(t *testing.T) {
|
||||
// Create a new user
|
||||
repo, _ := owl.CreateRepository(testRepoName())
|
||||
repo := getTestRepo()
|
||||
user, _ := repo.CreateUser(randomUserName())
|
||||
if _, err := os.Stat(path.Join(user.Dir(), "/public")); err != nil {
|
||||
t.Error("Public folder not created")
|
||||
|
@ -83,7 +83,7 @@ func TestCreateUserAddsPublicFolder(t *testing.T) {
|
|||
|
||||
func TestCanListRepoUsers(t *testing.T) {
|
||||
// Create a new user
|
||||
repo, _ := owl.CreateRepository(testRepoName())
|
||||
repo := getTestRepo()
|
||||
user1, _ := repo.CreateUser(randomUserName())
|
||||
user2, _ := repo.CreateUser(randomUserName())
|
||||
// Create a new post
|
||||
|
@ -121,7 +121,7 @@ func TestCannotOpenNonExisitingRepo(t *testing.T) {
|
|||
|
||||
func TestGetUser(t *testing.T) {
|
||||
// Create a new user
|
||||
repo, _ := owl.CreateRepository(testRepoName())
|
||||
repo := getTestRepo()
|
||||
user, _ := repo.CreateUser(randomUserName())
|
||||
// Get the user
|
||||
user2, err := repo.GetUser(user.Name())
|
||||
|
@ -135,7 +135,7 @@ func TestGetUser(t *testing.T) {
|
|||
|
||||
func TestCannotGetNonexistingUser(t *testing.T) {
|
||||
// Create a new user
|
||||
repo, _ := owl.CreateRepository(testRepoName())
|
||||
repo := getTestRepo()
|
||||
_, err := repo.GetUser(randomUserName())
|
||||
if err == nil {
|
||||
t.Error("No error returned when getting non-existing user")
|
||||
|
@ -144,7 +144,7 @@ func TestCannotGetNonexistingUser(t *testing.T) {
|
|||
|
||||
func TestGetStaticDirOfRepo(t *testing.T) {
|
||||
// Create a new user
|
||||
repo, _ := owl.CreateRepository(testRepoName())
|
||||
repo := getTestRepo()
|
||||
// Get the user
|
||||
staticDir := repo.StaticDir()
|
||||
if staticDir == "" {
|
||||
|
@ -154,7 +154,7 @@ func TestGetStaticDirOfRepo(t *testing.T) {
|
|||
|
||||
func TestNewRepoGetsStaticFiles(t *testing.T) {
|
||||
// Create a new user
|
||||
repo, _ := owl.CreateRepository(testRepoName())
|
||||
repo := getTestRepo()
|
||||
if _, err := os.Stat(repo.StaticDir()); err != nil {
|
||||
t.Error("Static directory not found")
|
||||
}
|
||||
|
@ -169,7 +169,7 @@ func TestNewRepoGetsStaticFiles(t *testing.T) {
|
|||
|
||||
func TestNewRepoGetsStaticFilesPicoCSSWithContent(t *testing.T) {
|
||||
// Create a new user
|
||||
repo, _ := owl.CreateRepository(testRepoName())
|
||||
repo := getTestRepo()
|
||||
file, err := os.Open(path.Join(repo.StaticDir(), "pico.min.css"))
|
||||
if err != nil {
|
||||
t.Error("Error opening pico.min.css")
|
||||
|
@ -183,7 +183,7 @@ func TestNewRepoGetsStaticFilesPicoCSSWithContent(t *testing.T) {
|
|||
|
||||
func TestNewRepoGetsBaseHtml(t *testing.T) {
|
||||
// Create a new user
|
||||
repo, _ := owl.CreateRepository(testRepoName())
|
||||
repo := getTestRepo()
|
||||
if _, err := os.Stat(path.Join(repo.Dir(), "/base.html")); err != nil {
|
||||
t.Error("Base html file not found")
|
||||
}
|
||||
|
@ -191,7 +191,7 @@ func TestNewRepoGetsBaseHtml(t *testing.T) {
|
|||
|
||||
func TestCanGetRepoTemplate(t *testing.T) {
|
||||
// Create a new user
|
||||
repo, _ := owl.CreateRepository(testRepoName())
|
||||
repo := getTestRepo()
|
||||
// Get the user
|
||||
template, err := repo.Template()
|
||||
if err != nil {
|
||||
|
@ -239,7 +239,7 @@ func TestSingleUserRepoUserUrlPathIsSimple(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCanGetMapWithAllPostAliases(t *testing.T) {
|
||||
repo, _ := owl.CreateRepository(testRepoName())
|
||||
repo := getTestRepo()
|
||||
user, _ := repo.CreateUser(randomUserName())
|
||||
post, _ := user.CreateNewPost("test-1")
|
||||
|
||||
|
@ -276,7 +276,7 @@ func TestCanGetMapWithAllPostAliases(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestAliasesHaveCorrectPost(t *testing.T) {
|
||||
repo, _ := owl.CreateRepository(testRepoName())
|
||||
repo := getTestRepo()
|
||||
user, _ := repo.CreateUser(randomUserName())
|
||||
post1, _ := user.CreateNewPost("test-1")
|
||||
post2, _ := user.CreateNewPost("test-2")
|
||||
|
|
6
user.go
6
user.go
|
@ -36,6 +36,11 @@ func (user User) FullUrl() string {
|
|||
return url
|
||||
}
|
||||
|
||||
func (user User) WebmentionUrl() string {
|
||||
url, _ := url.JoinPath(user.FullUrl(), "webmention/")
|
||||
return url
|
||||
}
|
||||
|
||||
func (user User) PostDir() string {
|
||||
return path.Join(user.Dir(), "public")
|
||||
}
|
||||
|
@ -150,6 +155,7 @@ func (user User) CreateNewPost(title string) (Post, error) {
|
|||
os.WriteFile(post.ContentFile(), []byte(initial_content), 0644)
|
||||
// create media dir
|
||||
os.Mkdir(post.MediaDir(), 0755)
|
||||
os.Mkdir(post.WebmentionDir(), 0755)
|
||||
return post, nil
|
||||
}
|
||||
|
||||
|
|
16
user_test.go
16
user_test.go
|
@ -11,7 +11,7 @@ import (
|
|||
|
||||
func TestCreateNewPostCreatesEntryInPublic(t *testing.T) {
|
||||
// Create a new user
|
||||
repo, _ := owl.CreateRepository(testRepoName())
|
||||
repo := getTestRepo()
|
||||
user, _ := repo.CreateUser(randomUserName())
|
||||
// Create a new post
|
||||
user.CreateNewPost("testpost")
|
||||
|
@ -26,7 +26,7 @@ func TestCreateNewPostCreatesEntryInPublic(t *testing.T) {
|
|||
|
||||
func TestCreateNewPostCreatesMediaDir(t *testing.T) {
|
||||
// Create a new user
|
||||
repo, _ := owl.CreateRepository(testRepoName())
|
||||
repo := getTestRepo()
|
||||
user, _ := repo.CreateUser(randomUserName())
|
||||
// Create a new post
|
||||
post, _ := user.CreateNewPost("testpost")
|
||||
|
@ -49,7 +49,7 @@ func TestCreateNewPostAddsDateToMetaBlock(t *testing.T) {
|
|||
|
||||
func TestCreateNewPostMultipleCalls(t *testing.T) {
|
||||
// Create a new user
|
||||
repo, _ := owl.CreateRepository(testRepoName())
|
||||
repo := getTestRepo()
|
||||
user, _ := repo.CreateUser(randomUserName())
|
||||
// Create a new post
|
||||
user.CreateNewPost("testpost")
|
||||
|
@ -66,7 +66,7 @@ func TestCreateNewPostMultipleCalls(t *testing.T) {
|
|||
|
||||
func TestCanListUserPosts(t *testing.T) {
|
||||
// Create a new user
|
||||
repo, _ := owl.CreateRepository(testRepoName())
|
||||
repo := getTestRepo()
|
||||
user, _ := repo.CreateUser(randomUserName())
|
||||
// Create a new post
|
||||
user.CreateNewPost("testpost")
|
||||
|
@ -83,7 +83,7 @@ func TestCanListUserPosts(t *testing.T) {
|
|||
|
||||
func TestCannotListUserPostsInSubdirectories(t *testing.T) {
|
||||
// Create a new user
|
||||
repo, _ := owl.CreateRepository(testRepoName())
|
||||
repo := getTestRepo()
|
||||
user, _ := repo.CreateUser(randomUserName())
|
||||
// Create a new post
|
||||
user.CreateNewPost("testpost")
|
||||
|
@ -120,7 +120,7 @@ func TestCannotListUserPostsInSubdirectories(t *testing.T) {
|
|||
|
||||
func TestCannotListUserPostsWithoutIndexMd(t *testing.T) {
|
||||
// Create a new user
|
||||
repo, _ := owl.CreateRepository(testRepoName())
|
||||
repo := getTestRepo()
|
||||
user, _ := repo.CreateUser(randomUserName())
|
||||
// Create a new post
|
||||
user.CreateNewPost("testpost")
|
||||
|
@ -149,7 +149,7 @@ func TestCannotListUserPostsWithoutIndexMd(t *testing.T) {
|
|||
|
||||
func TestListUserPostsDoesNotIncludeDrafts(t *testing.T) {
|
||||
// Create a new user
|
||||
repo, _ := owl.CreateRepository(testRepoName())
|
||||
repo := getTestRepo()
|
||||
user, _ := repo.CreateUser(randomUserName())
|
||||
// Create a new post
|
||||
post, _ := user.CreateNewPost("testpost")
|
||||
|
@ -170,7 +170,7 @@ func TestListUserPostsDoesNotIncludeDrafts(t *testing.T) {
|
|||
|
||||
func TestListUsersDraftsExcludedRealWorld(t *testing.T) {
|
||||
// Create a new user
|
||||
repo, _ := owl.CreateRepository(testRepoName())
|
||||
repo := getTestRepo()
|
||||
user, _ := repo.CreateUser(randomUserName())
|
||||
// Create a new post
|
||||
post, _ := user.CreateNewPost("testpost")
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
package owl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
type Webmention struct {
|
||||
Source string `yaml:"source"`
|
||||
Title string `yaml:"title"`
|
||||
ApprovalStatus string `yaml:"approval_status"`
|
||||
RetrievedAt time.Time `yaml:"retrieved_at"`
|
||||
}
|
||||
|
||||
type HttpRetriever interface {
|
||||
Get(url string) ([]byte, error)
|
||||
}
|
||||
|
||||
type MicroformatParser interface {
|
||||
ParseHEntry(data []byte) (ParsedHEntry, error)
|
||||
}
|
||||
|
||||
type OwlHttpRetriever struct{}
|
||||
|
||||
type OwlMicroformatParser struct{}
|
||||
|
||||
type ParsedHEntry struct {
|
||||
Title string
|
||||
}
|
||||
|
||||
func (OwlHttpRetriever) Get(url string) ([]byte, error) {
|
||||
resp, err := http.Get(url)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
var data []byte
|
||||
_, err = resp.Body.Read(data)
|
||||
// TODO: encoding
|
||||
return data, err
|
||||
}
|
||||
|
||||
func collectText(n *html.Node, buf *bytes.Buffer) {
|
||||
if n.Type == html.TextNode {
|
||||
buf.WriteString(n.Data)
|
||||
}
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
collectText(c, buf)
|
||||
}
|
||||
}
|
||||
|
||||
func (OwlMicroformatParser) ParseHEntry(data []byte) (ParsedHEntry, error) {
|
||||
doc, err := html.Parse(strings.NewReader(string(data)))
|
||||
if err != nil {
|
||||
return ParsedHEntry{}, err
|
||||
}
|
||||
|
||||
var interpretHFeed func(*html.Node, *ParsedHEntry, bool) (ParsedHEntry, error)
|
||||
interpretHFeed = func(n *html.Node, curr *ParsedHEntry, parent bool) (ParsedHEntry, error) {
|
||||
attrs := n.Attr
|
||||
for _, attr := range attrs {
|
||||
if attr.Key == "class" && strings.Contains(attr.Val, "p-name") {
|
||||
buf := &bytes.Buffer{}
|
||||
collectText(n, buf)
|
||||
curr.Title = buf.String()
|
||||
return *curr, nil
|
||||
}
|
||||
}
|
||||
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
interpretHFeed(c, curr, false)
|
||||
}
|
||||
return *curr, nil
|
||||
}
|
||||
|
||||
var findHFeed func(*html.Node) (ParsedHEntry, error)
|
||||
findHFeed = func(n *html.Node) (ParsedHEntry, error) {
|
||||
attrs := n.Attr
|
||||
for _, attr := range attrs {
|
||||
if attr.Key == "class" && strings.Contains(attr.Val, "h-entry") {
|
||||
return interpretHFeed(n, &ParsedHEntry{}, true)
|
||||
}
|
||||
}
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
entry, err := findHFeed(c)
|
||||
if err == nil {
|
||||
return entry, nil
|
||||
}
|
||||
}
|
||||
return ParsedHEntry{}, errors.New("no h-entry found")
|
||||
}
|
||||
return findHFeed(doc)
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package owl_test
|
||||
|
||||
import (
|
||||
"h4kor/owl-blogs"
|
||||
"testing"
|
||||
)
|
||||
|
||||
//
|
||||
// https://www.w3.org/TR/webmention/#h-webmention-verification
|
||||
//
|
||||
|
||||
func TestParseValidHEntry(t *testing.T) {
|
||||
html := []byte("<div class=\"h-entry\"><div class=\"p-name\">Foo</div></div>")
|
||||
parser := &owl.OwlMicroformatParser{}
|
||||
entry, err := parser.ParseHEntry(html)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Unable to parse feed: %v", err)
|
||||
}
|
||||
if entry.Title != "Foo" {
|
||||
t.Errorf("Wrong Title. Expected %v, got %v", "Foo", entry.Title)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseValidHEntryWithoutTitle(t *testing.T) {
|
||||
html := []byte("<div class=\"h-entry\"></div><div class=\"p-name\">Foo</div>")
|
||||
parser := &owl.OwlMicroformatParser{}
|
||||
entry, err := parser.ParseHEntry(html)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Unable to parse feed: %v", err)
|
||||
}
|
||||
if entry.Title != "" {
|
||||
t.Errorf("Wrong Title. Expected %v, got %v", "Foo", entry.Title)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue