Skip to content

Commit

Permalink
Tie loose ends in YT tests, add better errors
Browse files Browse the repository at this point in the history
  • Loading branch information
fakelag committed Feb 25, 2024
1 parent 9dbf42e commit ade7d55
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 48 deletions.
31 changes: 31 additions & 0 deletions testutils/mockExecutor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package testutils

import (
"errors"
"fmt"
"time"
)

type MockCommandExecutor struct {
MockStdoutResult string
MockExitCode int
}

func (command *MockCommandExecutor) RunCommandWithTimeout(
executable string,
timeout time.Duration,
args ...string,
) (chan *string, chan error) {
resultChannel := make(chan *string, 1)
errorChannel := make(chan error, 1)

go func() {
if command.MockExitCode != 0 {
errorChannel <- errors.New(fmt.Sprintf("exit status %d", command.MockExitCode))
} else {
resultChannel <- &command.MockStdoutResult
}
}()

return resultChannel, errorChannel
}
23 changes: 12 additions & 11 deletions youtubeapi/youtube.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package youtubeapi
import (
"encoding/json"
"errors"
"fmt"
"math/rand"
"net/url"
"os/exec"
Expand All @@ -14,7 +13,13 @@ import (

cmd "github.com/fakelag/streaming-music-bot/command"
"github.com/fakelag/streaming-music-bot/entities"
"github.com/fakelag/streaming-music-bot/utils"
)

var (
ErrorUnrecognisedObject = errors.New("unrecognised object type")
ErrorInvalidYtdlpData = errors.New("invalid ytdlp data")
ErrorNoVideoFound = errors.New("no video found")
ErrorNoPlaylistFound = errors.New("no playlist found")
)

type YtDlpObject struct {
Expand Down Expand Up @@ -124,17 +129,13 @@ func (yt *Youtube) GetYoutubeMedia(videoIdOrSearchTerm string) (*YoutubeMedia, e
}

if len(stdout) == 0 {
return nil, errors.New("No video found")
return nil, ErrorNoVideoFound
}

urlAndJson := strings.Split(stdout, "\n")

if len(urlAndJson) < 2 {
firstString := ""
if len(urlAndJson) > 0 {
firstString = urlAndJson[0]
}
return nil, errors.New(fmt.Sprintf("Invalid video json data: %s", utils.TruncateString(firstString, 50, "...")))
return nil, ErrorInvalidYtdlpData
}

videoStreamURL := urlAndJson[0]
Expand Down Expand Up @@ -175,7 +176,7 @@ func (yt *Youtube) GetYoutubeMedia(videoIdOrSearchTerm string) (*YoutubeMedia, e

return media, nil
} else {
return nil, errors.New(fmt.Sprintf("Unrecognised object type %s", object.Type))
return nil, ErrorUnrecognisedObject
}
}

Expand Down Expand Up @@ -216,7 +217,7 @@ func (yt *Youtube) GetYoutubePlaylist(playlistIdOrUrl string) (*YoutubePlaylist,
}

if len(playlistJson) == 0 {
return nil, errors.New("No playlist found")
return nil, ErrorNoPlaylistFound
}

var object YtDlpObject
Expand All @@ -225,7 +226,7 @@ func (yt *Youtube) GetYoutubePlaylist(playlistIdOrUrl string) (*YoutubePlaylist,
}

if object.Type != "playlist" {
return nil, errors.New(fmt.Sprintf("Unrecognised object type %s", object.Type))
return nil, ErrorUnrecognisedObject
}

var ytDlpPlaylist YtDlpPlayList
Expand Down
4 changes: 4 additions & 0 deletions youtubeapi/youtube_media.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,9 @@ func (ytm *YoutubeMedia) IsLiveStream() bool {
return ytm.VideoIsLiveStream
}

func (ytm *YoutubeMedia) SetYtAPI(ytAPI *Youtube) {
ytm.ytAPI = ytAPI
}

// Verify implements entities.Media
var _ entities.Media = (*YoutubeMedia)(nil)
103 changes: 103 additions & 0 deletions youtubeapi/youtube_media_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package youtubeapi_test

import (
"fmt"
"strings"
"time"

"github.com/fakelag/streaming-music-bot/testutils"
"github.com/fakelag/streaming-music-bot/youtubeapi"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func NewYoutubeMedia(ytAPI *youtubeapi.Youtube) *youtubeapi.YoutubeMedia {
expireAt := time.Now().Add(10 * time.Minute)
media := &youtubeapi.YoutubeMedia{
ID: "1",
VideoTitle: "Mock Media 1",
VideoLink: "foobar",
VideoIsLiveStream: false,
VideoDuration: 60 * time.Second,
StreamURL: "streamurl1",
StreamExpiresAt: &expireAt,
}

media.SetYtAPI(ytAPI)

return media
}

var _ = Describe("YT Media", func() {
var videoJson string = strings.ReplaceAll(`{
"id": "1",
"fulltitle": "Mock Media 1",
"duration": 60,
"thumbnail": "foo",
"is_live": false,
"_type": "video"
}`, "\n", "")

var expireTimeUnix int64 = 1706280000
var streamUrl string = "https://manifest.googlevideo.com/api/manifest/hls_playlist/expire/" + fmt.Sprintf("%d", expireTimeUnix) + "/ei&foo=bar"

When("Reloading a media file", func() {
It("Reloads media when it is not loaded and calling EnsureLoaded", func() {
mockExecutor := &testutils.MockCommandExecutor{
MockStdoutResult: streamUrl + "\n" + videoJson,
}

yt := youtubeapi.NewYoutubeAPI()
yt.SetCmdExecutor(mockExecutor)

media := NewYoutubeMedia(yt)
media.StreamExpiresAt = nil
media.StreamURL = ""

Expect(media.EnsureLoaded()).To(Succeed())
Expect(media.FileURLExpiresAt()).NotTo(BeNil())
Expect(*media.FileURLExpiresAt()).To(BeTemporally("~", time.Unix(expireTimeUnix, 0), time.Second))
Expect(media.FileURL()).To(Equal(streamUrl))
Expect(media.CanJumpToTimeStamp()).To(BeTrue())
})

It("Reloads media when it is loaded but expired and calling EnsureLoaded", func() {
newExpireTime := expireTimeUnix + 10 // +10s
newFileURL := "https://manifest.googlevideo.com/api/manifest/hls_playlist/expire/" + fmt.Sprintf("%d", newExpireTime) + "/ei&foo=bar"
mockExecutor := &testutils.MockCommandExecutor{
MockStdoutResult: newFileURL + "\n" + videoJson,
}

yt := youtubeapi.NewYoutubeAPI()
yt.SetCmdExecutor(mockExecutor)

media := NewYoutubeMedia(yt)

expireAt := time.Now().Add(10 * time.Second)
media.StreamExpiresAt = &expireAt
media.StreamURL = streamUrl

Expect(media.EnsureLoaded()).To(Succeed())

Expect(media.FileURLExpiresAt()).NotTo(BeNil())
Expect(*media.FileURLExpiresAt()).To(BeTemporally("~", time.Unix(newExpireTime, 0), time.Second))
Expect(media.FileURL()).To(Equal(newFileURL))
})

It("Returns a sensible error when yt api fails during EnsureLoaded", func() {
mockExecutor := &testutils.MockCommandExecutor{
MockStdoutResult: streamUrl + "\n" + "{\"_type\": \"something\"}",
}

yt := youtubeapi.NewYoutubeAPI()
yt.SetCmdExecutor(mockExecutor)

media := NewYoutubeMedia(yt)
media.StreamExpiresAt = nil
media.StreamURL = ""

Expect(media.EnsureLoaded()).To(MatchError(youtubeapi.ErrorUnrecognisedObject))
})
})
})
9 changes: 9 additions & 0 deletions youtubeapi/youtube_playlist_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ var _ = Describe("YT Playlists", func() {
Expect(playList.Link()).To(Equal("listurl"))
Expect(playList.GetDurationLeft()).NotTo(BeNil())
Expect(*playList.GetDurationLeft()).To(Equal(60 * time.Second))
Expect(playList.GetAvailableConsumeOrders()).To(
ContainElements([]entities.PlaylistConsumeOrder{entities.ConsumeOrderFromStart, entities.ConsumeOrderShuffle}),
)

media, err := playList.ConsumeNextMedia()
Expect(err).ToNot(HaveOccurred())
Expand Down Expand Up @@ -142,5 +145,11 @@ var _ = Describe("YT Playlists", func() {
Expect(media).NotTo(BeNil())
Expect(playList.GetMediaCount()).To(Equal(2))
})

It("Gives sensible errors when attempting to configure playlist invalidly", func() {
playList := NewPlaylistWithMedia()
Expect(playList.SetConsumeOrder(entities.PlaylistConsumeOrder("nonexistent_consume_order"))).
To(MatchError(entities.ErrorConsumeOrderNotSupported))
})
})
})
Loading

0 comments on commit ade7d55

Please sign in to comment.