add unittests

This commit is contained in:
Sinuhe Tellez
2021-05-06 02:48:02 -04:00
parent c09464abc7
commit 97a28d7239
12 changed files with 357 additions and 142 deletions

158
internal/service/service.go Normal file
View File

@@ -0,0 +1,158 @@
//package service provides a http handler that reads the path in the request.url and returns
// an xml document that follows the OPDS 1.1 standard
// https://specs.opds.io/opds-1.1.html
package service
import (
"bytes"
"encoding/xml"
"io/ioutil"
"log"
"mime"
"net/http"
"os"
"path"
"path/filepath"
"time"
"github.com/dubyte/dir2opds/opds"
"golang.org/x/tools/blog/atom"
)
func init() {
_ = mime.AddExtensionType(".mobi", "application/x-mobipocket-ebook")
_ = mime.AddExtensionType(".epub", "application/epub+zip")
_ = mime.AddExtensionType(".cbz", "application/x-cbz")
_ = mime.AddExtensionType(".cbr", "application/x-cbr")
_ = mime.AddExtensionType(".fb2", "text/fb2+xml")
}
type OPDS struct {
DirRoot string
Author string
AuthorEmail string
AuthorURI string
}
var TimeNowFunc = timeNow
func (s OPDS) Handler(w http.ResponseWriter, req *http.Request) error {
fPath := path.Join(s.DirRoot, req.URL.Path)
log.Printf("fPath:'%s'", fPath)
fi, err := os.Stat(fPath)
if err != nil {
return err
}
if isFile(fi) {
http.ServeFile(w, req, fPath)
return nil
}
content, err := s.getContent(req, fPath)
if err != nil {
return err
}
content = append([]byte(xml.Header), content...)
http.ServeContent(w, req, "feed.xml", TimeNowFunc(), bytes.NewReader(content))
return nil
}
func (s OPDS) getContent(req *http.Request, dirpath string) (result []byte, err error) {
feed := s.makeFeed(dirpath, req)
if getPathType(dirpath) == pathTypeDirOfFiles {
acFeed := &opds.AcquisitionFeed{&feed, "http://purl.org/dc/terms/", "http://opds-spec.org/2010/catalog"}
result, err = xml.MarshalIndent(acFeed, " ", " ")
} else {
result, err = xml.MarshalIndent(feed, " ", " ")
}
return
}
const navigationType = "application/atom+xml;profile=opds-catalog;kind=navigation"
func (s OPDS) makeFeed(dirpath string, req *http.Request) atom.Feed {
feedBuilder := opds.FeedBuilder.
ID(req.URL.Path).
Title("Catalog in " + req.URL.Path).
Author(opds.AuthorBuilder.Name(s.Author).Email(s.AuthorEmail).URI(s.AuthorURI).Build()).
Updated(TimeNowFunc()).
AddLink(opds.LinkBuilder.Rel("start").Href("/").Type(navigationType).Build())
fis, _ := ioutil.ReadDir(dirpath)
for _, fi := range fis {
pathType := getPathType(path.Join(dirpath, fi.Name()))
feedBuilder = feedBuilder.
AddEntry(opds.EntryBuilder.
ID(req.URL.Path + fi.Name()).
Title(fi.Name()).
Updated(TimeNowFunc()).
Published(TimeNowFunc()).
AddLink(opds.LinkBuilder.
Rel(getRel(fi.Name(), pathType)).
Title(fi.Name()).
Href(getHref(req, fi.Name())).
Type(getType(fi.Name(), pathType)).
Build()).
Build())
}
return feedBuilder.Build()
}
func getRel(name string, pathType int) string {
if pathType == pathTypeDirOfFiles || pathType == pathTypeDirOfDirs {
return "subsection"
}
ext := filepath.Ext(name)
if ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".gif" {
return "http://opds-spec.org/image/thumbnail"
}
// mobi, epub, etc
return "http://opds-spec.org/acquisition"
}
func getType(name string, pathType int) string {
if pathType == pathTypeFile {
return mime.TypeByExtension(filepath.Ext(name))
}
return "application/atom+xml;profile=opds-catalog;kind=acquisition"
}
func getHref(req *http.Request, name string) string {
return path.Join(req.URL.RequestURI(), name)
}
const (
pathTypeFile = iota
pathTypeDirOfDirs
pathTypeDirOfFiles
)
func getPathType(dirpath string) int {
fi, _ := os.Stat(dirpath)
if isFile(fi) {
return pathTypeFile
}
fis, _ := ioutil.ReadDir(dirpath)
for _, fi := range fis {
if isFile(fi) {
return pathTypeDirOfFiles
}
}
// Directory of directories
return pathTypeDirOfDirs
}
func isFile(fi os.FileInfo) bool {
return !fi.IsDir()
}
func timeNow() time.Time {
return time.Now()
}

View File

@@ -0,0 +1,115 @@
package service_test
import (
"io"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/dubyte/dir2opds/internal/service"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestHandler(t *testing.T) {
// pre-setup
nowFn := service.TimeNowFunc
defer func() {
service.TimeNowFunc = nowFn
}()
tests := map[string]struct {
input string
want string
WantedContentType string
}{
"feed (dir of folders )": {input: "/", want: feed, WantedContentType: "application/xml"},
"acquisitionFeed(dir of files)": {input: "/mybook", want: acquisitionFeed, WantedContentType: "application/xml"},
"servingAFile": {input: "/mybook/mybook.txt", want: "Fixture", WantedContentType: "text/plain; charset=utf-8"},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
// setup
s := service.OPDS{"testdata", "", "", ""}
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, tc.input, nil)
service.TimeNowFunc = func() time.Time {
return time.Date(2020, 05, 25, 00, 00, 00, 0, time.UTC)
}
// act
err := s.Handler(w, req)
require.NoError(t, err)
// post act
resp := w.Result()
body, err := io.ReadAll(resp.Body)
require.NoError(t, err)
// verify
assert.Equal(t, 200, resp.StatusCode)
assert.Equal(t, tc.WantedContentType, resp.Header.Get("Content-Type"))
assert.Equal(t, tc.want, string(body))
})
}
}
var feed = `<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Catalog in /</title>
<id>/</id>
<link rel="start" href="/" type="application/atom+xml;profile=opds-catalog;kind=navigation"></link>
<updated>2020-05-25T00:00:00+00:00</updated>
<author>
<name></name>
</author>
<entry>
<title>emptyFolder</title>
<id>/emptyFolder</id>
<link rel="subsection" href="/emptyFolder" type="application/atom+xml;profile=opds-catalog;kind=acquisition" title="emptyFolder"></link>
<published>2020-05-25T00:00:00+00:00</published>
<updated>2020-05-25T00:00:00+00:00</updated>
</entry>
<entry>
<title>mybook</title>
<id>/mybook</id>
<link rel="subsection" href="/mybook" type="application/atom+xml;profile=opds-catalog;kind=acquisition" title="mybook"></link>
<published>2020-05-25T00:00:00+00:00</published>
<updated>2020-05-25T00:00:00+00:00</updated>
</entry>
</feed>`
var acquisitionFeed = `<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/terms/" xmlns:opds="http://opds-spec.org/2010/catalog">
<title>Catalog in /mybook</title>
<id>/mybook</id>
<link rel="start" href="/" type="application/atom+xml;profile=opds-catalog;kind=navigation"></link>
<updated>2020-05-25T00:00:00+00:00</updated>
<author>
<name></name>
</author>
<entry>
<title>mybook.epub</title>
<id>/mybookmybook.epub</id>
<link rel="http://opds-spec.org/acquisition" href="/mybook/mybook.epub" type="application/epub+zip" title="mybook.epub"></link>
<published>2020-05-25T00:00:00+00:00</published>
<updated>2020-05-25T00:00:00+00:00</updated>
</entry>
<entry>
<title>mybook.pdf</title>
<id>/mybookmybook.pdf</id>
<link rel="http://opds-spec.org/acquisition" href="/mybook/mybook.pdf" type="application/pdf" title="mybook.pdf"></link>
<published>2020-05-25T00:00:00+00:00</published>
<updated>2020-05-25T00:00:00+00:00</updated>
</entry>
<entry>
<title>mybook.txt</title>
<id>/mybookmybook.txt</id>
<link rel="http://opds-spec.org/acquisition" href="/mybook/mybook.txt" type="text/plain; charset=utf-8" title="mybook.txt"></link>
<published>2020-05-25T00:00:00+00:00</published>
<updated>2020-05-25T00:00:00+00:00</updated>
</entry>
</feed>`

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1 @@
Fixture