Files
gonic/server/ctrlsubsonic/handlers_raw_test.go
2022-04-13 01:10:46 +01:00

153 lines
4.2 KiB
Go

package ctrlsubsonic
import (
"fmt"
"io/fs"
"net/http"
"net/url"
"os"
"os/exec"
"strconv"
"testing"
"time"
"github.com/matryer/is"
"go.senan.xyz/gonic/db"
"go.senan.xyz/gonic/transcode"
)
func TestServeStreamRaw(t *testing.T) {
t.Parallel()
if _, err := exec.LookPath("ffmpeg"); err != nil {
t.Skipf("no ffmpeg in $PATH")
}
is := is.New(t)
contr := makeControllerAudio(t)
statFlac := stat(t, audioPath10s)
rr, req := makeHTTPMock(url.Values{"id": {"tr-1"}})
serveRaw(t, contr, contr.ServeStream, rr, req)
is.Equal(rr.Code, http.StatusOK)
is.Equal(rr.Header().Get("content-type"), "audio/flac")
is.Equal(atoi(t, rr.Header().Get("content-length")), int(statFlac.Size()))
is.Equal(atoi(t, rr.Header().Get("content-length")), rr.Body.Len())
}
func TestServeStreamOpus(t *testing.T) {
t.Parallel()
if _, err := exec.LookPath("ffmpeg"); err != nil {
t.Skipf("no ffmpeg in $PATH")
}
is := is.New(t)
contr := makeControllerAudio(t)
var user db.User
is.NoErr(contr.DB.Where("name=?", mockUsername).Find(&user).Error)
is.NoErr(contr.DB.Create(&db.TranscodePreference{UserID: user.ID, Client: mockClientName, Profile: "opus"}).Error)
rr, req := makeHTTPMock(url.Values{"id": {"tr-1"}})
serveRaw(t, contr, contr.ServeStream, rr, req)
is.Equal(rr.Code, http.StatusOK)
is.Equal(rr.Header().Get("content-type"), "audio/ogg")
is.Equal(atoi(t, rr.Header().Get("content-length")), transcode.GuessExpectedSize(transcode.Opus, 10*time.Second))
is.Equal(atoi(t, rr.Header().Get("content-length")), rr.Body.Len())
}
func TestServeStreamOpusMaxBitrate(t *testing.T) {
t.Parallel()
if _, err := exec.LookPath("ffmpeg"); err != nil {
t.Skipf("no ffmpeg in $PATH")
}
is := is.New(t)
contr := makeControllerAudio(t)
var user db.User
is.NoErr(contr.DB.Where("name=?", mockUsername).Find(&user).Error)
is.NoErr(contr.DB.Create(&db.TranscodePreference{UserID: user.ID, Client: mockClientName, Profile: "opus"}).Error)
const bitrate = 5
rr, req := makeHTTPMock(url.Values{"id": {"tr-1"}, "maxBitRate": {strconv.Itoa(bitrate)}})
serveRaw(t, contr, contr.ServeStream, rr, req)
profile := transcode.WithBitrate(transcode.Opus, transcode.BitRate(bitrate))
expectedLength := transcode.GuessExpectedSize(profile, 10*time.Second)
is.Equal(rr.Code, http.StatusOK)
is.Equal(rr.Header().Get("content-type"), "audio/ogg")
is.Equal(atoi(t, rr.Header().Get("content-length")), expectedLength)
is.Equal(atoi(t, rr.Header().Get("content-length")), rr.Body.Len())
}
func TestServeStreamMP3Range(t *testing.T) {
t.Parallel()
if _, err := exec.LookPath("ffmpeg"); err != nil {
t.Skipf("no ffmpeg in $PATH")
}
is := is.New(t)
contr := makeControllerAudio(t)
var user db.User
is.NoErr(contr.DB.Where("name=?", mockUsername).Find(&user).Error)
is.NoErr(contr.DB.Create(&db.TranscodePreference{UserID: user.ID, Client: mockClientName, Profile: "mp3"}).Error)
var totalBytes []byte
{
rr, req := makeHTTPMock(url.Values{"id": {"tr-1"}})
serveRaw(t, contr, contr.ServeStream, rr, req)
is.Equal(rr.Code, http.StatusOK)
is.Equal(rr.Header().Get("content-type"), "audio/mpeg")
totalBytes = rr.Body.Bytes()
}
const chunkSize = 2 << 16
var bytes []byte
for i := 0; i < len(totalBytes); i += chunkSize {
rr, req := makeHTTPMock(url.Values{"id": {"tr-1"}})
req.Header.Set("range", fmt.Sprintf("bytes=%d-%d", i, min(i+chunkSize, len(totalBytes))-1))
t.Log(req.Header.Get("range"))
serveRaw(t, contr, contr.ServeStream, rr, req)
is.Equal(rr.Code, http.StatusPartialContent)
is.Equal(rr.Header().Get("content-type"), "audio/mpeg")
is.True(atoi(t, rr.Header().Get("content-length")) == chunkSize || atoi(t, rr.Header().Get("content-length")) == len(totalBytes)%chunkSize)
is.Equal(atoi(t, rr.Header().Get("content-length")), rr.Body.Len())
bytes = append(bytes, rr.Body.Bytes()...)
}
is.Equal(len(totalBytes), len(bytes))
is.Equal(totalBytes, bytes)
}
func stat(t *testing.T, path string) fs.FileInfo {
t.Helper()
info, err := os.Stat(path)
if err != nil {
t.Fatalf("stat %q: %v", path, err)
}
return info
}
func atoi(t *testing.T, in string) int {
t.Helper()
i, err := strconv.Atoi(in)
if err != nil {
t.Fatalf("atoi %q: %v", in, err)
}
return i
}
func min(a, b int) int {
if a < b {
return a
}
return b
}