test: add unit tests

This commit is contained in:
rramiachraf 2024-03-06 20:53:29 +01:00
parent c8747c0182
commit afc0347a1b
19 changed files with 358 additions and 31 deletions

18
.github/workflows/test.yml vendored Normal file
View File

@ -0,0 +1,18 @@
name: Test and Build
on:
push:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
- name: Install dependencies
run: go get .
- name: Build
run: make build
- name: Test
run: make test

View File

@ -8,5 +8,7 @@ build:gentempl esbuild
templ generate
cat ./style/*.css | ./esbuild --loader=css --minify > ./static/style.css
go build -ldflags="-X 'github.com/rramiachraf/dumb/data.Version=$(VERSION)' -s -w"
test:
go test ./... -v
fmt:
templ fmt .

View File

@ -12,7 +12,7 @@ import (
"github.com/sirupsen/logrus"
)
func Album(l *logrus.Logger) http.HandlerFunc {
func album(l *logrus.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
artist := mux.Vars(r)["artist"]
albumName := mux.Vars(r)["albumName"]

39
handlers/album_test.go Normal file
View File

@ -0,0 +1,39 @@
package handlers
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/PuerkitoBio/goquery"
"github.com/sirupsen/logrus"
)
func TestAlbum(t *testing.T) {
url := "/albums/Daft-punk/Random-access-memories"
title := "Give Life Back to Music"
r, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
l := logrus.New()
m := New(l)
m.ServeHTTP(rr, r)
defer rr.Result().Body.Close()
doc, err := goquery.NewDocumentFromReader(rr.Result().Body)
if err != nil {
t.Fatal(err)
}
docTitle := doc.Find("#album-tracklist > a > p").First().Text()
if docTitle != title {
t.Fatalf("expected %q, got %q\n", title, docTitle)
}
}

View File

@ -16,7 +16,7 @@ import (
"github.com/sirupsen/logrus"
)
func Annotations(l *logrus.Logger) http.HandlerFunc {
func annotations(l *logrus.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]
if a, err := getCache[data.Annotation]("annotation:" + id); err == nil {

View File

@ -0,0 +1,38 @@
package handlers
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/sirupsen/logrus"
)
func TestAnnotations(t *testing.T) {
url := "/61590/Black-star-respiration/The-new-moon-rode-high-in-the-crown-of-the-metropolis/annotations"
r, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
l := logrus.New()
m := New(l)
m.ServeHTTP(rr, r)
defer rr.Result().Body.Close()
decoder := json.NewDecoder(rr.Result().Body)
annotation := map[string]string{}
if err := decoder.Decode(&annotation); err != nil {
t.Fatal(err)
}
if _, exists := annotation["html"]; !exists {
t.Fatalf("html field not found on annotation\n")
}
}

25
handlers/cache_test.go Normal file
View File

@ -0,0 +1,25 @@
package handlers
import (
"bytes"
"testing"
)
func TestCache(t *testing.T) {
key := "testkey"
value := []byte("testvalue")
err := setCache(key, value)
if err != nil {
t.Fatalf("unable to set cache, %q\n", err)
}
v, err := getCache[[]byte](key)
if err != nil {
t.Fatalf("unable to get cache, %q\n", err)
}
if !bytes.Equal(v, value) {
t.Fatalf("expected %q, got %q\n", value, v)
}
}

32
handlers/handler.go Normal file
View File

@ -0,0 +1,32 @@
package handlers
import (
"context"
"net/http"
"github.com/a-h/templ"
"github.com/gorilla/mux"
"github.com/rramiachraf/dumb/views"
"github.com/sirupsen/logrus"
)
func New(logger *logrus.Logger) *mux.Router {
r := mux.NewRouter()
r.Use(mustHeaders)
r.Handle("/", templ.Handler(views.HomePage()))
r.HandleFunc("/{id}-lyrics", lyrics(logger)).Methods("GET")
r.HandleFunc("/albums/{artist}/{albumName}", album(logger)).Methods("GET")
r.HandleFunc("/images/{filename}.{ext}", imageProxy(logger)).Methods("GET")
r.HandleFunc("/search", search(logger)).Methods("GET")
r.HandleFunc("/{id}/{artist-song}/{verse}/annotations", annotations(logger)).Methods("GET")
r.HandleFunc("/instances.json", instances(logger)).Methods("GET")
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
r.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
views.ErrorPage(404, "page not found").Render(context.Background(), w)
})
return r
}

View File

@ -20,7 +20,7 @@ func sendError(err error, status int, msg string, l *logrus.Logger, w http.Respo
}
}
func Instances(l *logrus.Logger) http.HandlerFunc {
func instances(l *logrus.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if instances, err := getCache[[]byte]("instances"); err == nil {
w.Header().Set("content-type", ContentTypeJSON)

View File

@ -0,0 +1,40 @@
package handlers
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/sirupsen/logrus"
)
func TestInstancesList(t *testing.T) {
r, err := http.NewRequest(http.MethodGet, "/instances.json", nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
l := logrus.New()
m := New(l)
m.ServeHTTP(rr, r)
c := rr.Result().Header.Get("content-type")
if c != ContentTypeJSON {
t.Fatalf("expected %q, got %q", ContentTypeJSON, c)
}
defer rr.Result().Body.Close()
d := json.NewDecoder(rr.Result().Body)
instances := []map[string]any{}
if err := d.Decode(&instances); err != nil {
t.Fatalf("unable to decode json from response, %q\n", err)
}
if _, exists := instances[0]["clearnet"]; !exists {
t.Fatal("unable to get clearnet value from instances list")
}
}

View File

@ -12,7 +12,7 @@ import (
"github.com/sirupsen/logrus"
)
func Lyrics(l *logrus.Logger) http.HandlerFunc {
func lyrics(l *logrus.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id := mux.Vars(r)["id"]

45
handlers/lyrics_test.go Normal file
View File

@ -0,0 +1,45 @@
package handlers
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/PuerkitoBio/goquery"
"github.com/sirupsen/logrus"
)
func TestLyrics(t *testing.T) {
url := "/The-silver-seas-catch-yer-own-train-lyrics"
title := "The Silver Seas"
artist := "Catch Yer Own Train"
r, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
l := logrus.New()
m := New(l)
m.ServeHTTP(rr, r)
defer rr.Result().Body.Close()
doc, err := goquery.NewDocumentFromReader(rr.Result().Body)
if err != nil {
t.Fatal(err)
}
docTitle := doc.Find("#metadata-info > h2").Text()
docArtist := doc.Find("#metadata-info > h1").Text()
if docTitle != title {
t.Fatalf("expected %q, got %q\n", title, docTitle)
}
if docArtist != artist {
t.Fatalf("expected %q, got %q\n", artist, docArtist)
}
}

View File

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"io"
"mime"
"net/http"
"strings"
@ -23,7 +24,7 @@ func isValidExt(ext string) bool {
return false
}
func ImageProxy(l *logrus.Logger) http.HandlerFunc {
func imageProxy(l *logrus.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
v := mux.Vars(r)
f := v["filename"]
@ -52,7 +53,7 @@ func ImageProxy(l *logrus.Logger) http.HandlerFunc {
return
}
w.Header().Add("Content-type", fmt.Sprintf("image/%s", ext))
w.Header().Add("Content-type", mime.TypeByExtension("."+ext))
w.Header().Add("Cache-Control", "max-age=1296000")
if _, err = io.Copy(w, res.Body); err != nil {
l.Errorln("unable to write image", err)

38
handlers/proxy_test.go Normal file
View File

@ -0,0 +1,38 @@
package handlers
import (
"mime"
"net/http"
"net/http/httptest"
"testing"
"github.com/sirupsen/logrus"
)
func TestImageProxy(t *testing.T) {
imgURL := "/images/3852401ae6c6d6a51aafe814d67199f0.1000x1000x1.jpg"
r, err := http.NewRequest(http.MethodGet, imgURL, nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
l := logrus.New()
m := New(l)
m.ServeHTTP(rr, r)
cc := rr.Result().Header.Get("cache-control")
maxAge := "max-age=1296000"
ct := rr.Result().Header.Get("content-type")
mimeType := mime.TypeByExtension(".jpg")
if cc != maxAge {
t.Fatalf("expected %q, got %q\n", maxAge, cc)
}
if ct != mimeType {
t.Fatalf("expected %q, got %q\n", mimeType, ct)
}
}

View File

@ -12,7 +12,7 @@ import (
"github.com/sirupsen/logrus"
)
func Search(l *logrus.Logger) http.HandlerFunc {
func search(l *logrus.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query().Get("q")
url := fmt.Sprintf(`https://genius.com/api/search/multi?q=%s`, url.QueryEscape(query))

38
handlers/search_test.go Normal file
View File

@ -0,0 +1,38 @@
package handlers
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/PuerkitoBio/goquery"
"github.com/sirupsen/logrus"
)
func TestSearch(t *testing.T) {
url := "/search?q=it+aint+hard+to+tell"
artist := "Nas"
r, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
l := logrus.New()
m := New(l)
m.ServeHTTP(rr, r)
defer rr.Result().Body.Close()
doc, err := goquery.NewDocumentFromReader(rr.Result().Body)
if err != nil {
t.Fatal(err)
}
docArtist := doc.Find("#search-item > div > span").First().Text()
if docArtist != artist {
t.Fatalf("expected %q, got %q\n", artist, docArtist)
}
}

View File

@ -9,7 +9,7 @@ import (
fhttp "github.com/Danny-Dasilva/fhttp"
)
func MustHeaders(next http.Handler) http.Handler {
func mustHeaders(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
csp := "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'; object-src 'none'"
w.Header().Add("content-security-policy", csp)

32
handlers/utils_test.go Normal file
View File

@ -0,0 +1,32 @@
package handlers
/*
import (
"encoding/json"
"testing"
)
func TestSendRequest(t *testing.T) {
res, err := sendRequest("https://tls.peet.ws/api/clean")
if err != nil {
t.Fatal(err)
}
defer res.Body.Close()
type fingerprint struct {
JA3 string `json:"ja3"`
}
decoder := json.NewDecoder(res.Body)
var fg fingerprint
if err := decoder.Decode(&fg); err != nil {
t.Fatal(err)
}
if fg.JA3 != JA3 {
t.Fatalf("expected %q, got %q\n", JA3, fg.JA3)
}
}
*/

25
main.go
View File

@ -1,7 +1,6 @@
package main
import (
"context"
"fmt"
"net"
"net/http"
@ -9,35 +8,15 @@ import (
"strconv"
"time"
"github.com/a-h/templ"
"github.com/gorilla/mux"
"github.com/rramiachraf/dumb/handlers"
"github.com/rramiachraf/dumb/views"
"github.com/sirupsen/logrus"
)
var logger = logrus.New()
func main() {
r := mux.NewRouter()
r.Use(handlers.MustHeaders)
r.Handle("/", templ.Handler(views.HomePage()))
r.HandleFunc("/{id}-lyrics", handlers.Lyrics(logger)).Methods("GET")
r.HandleFunc("/albums/{artist}/{albumName}", handlers.Album(logger)).Methods("GET")
r.HandleFunc("/images/{filename}.{ext}", handlers.ImageProxy(logger)).Methods("GET")
r.HandleFunc("/search", handlers.Search(logger)).Methods("GET")
r.HandleFunc("/{id}/{artist-song}/{verse}/annotations", handlers.Annotations(logger)).Methods("GET")
r.HandleFunc("/instances.json", handlers.Instances(logger)).Methods("GET")
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
r.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
views.ErrorPage(404, "page not found").Render(context.Background(), w)
})
var logger = logrus.New()
server := &http.Server{
Handler: r,
Handler: handlers.New(logger),
WriteTimeout: 25 * time.Second,
ReadTimeout: 25 * time.Second,
}