mirror of
https://github.com/5rahim/seanime
synced 2026-04-18 22:24:55 +02:00
wip(tests): mock anilist client, local files, collection
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,6 +11,7 @@ web/
|
||||
out/
|
||||
assets/
|
||||
|
||||
test/testdata/**/*.json
|
||||
test/sample
|
||||
test/providers.json
|
||||
test/db.json
|
||||
|
||||
@@ -9,13 +9,34 @@ import (
|
||||
"github.com/Yamashou/gqlgenc/graphqljson"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/seanime-app/seanime/internal/limiter"
|
||||
"github.com/seanime-app/seanime/internal/util"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ClientWrapperInterface interface {
|
||||
UpdateEntry(ctx context.Context, mediaID *int, status *MediaListStatus, score *float64, progress *int, repeat *int, private *bool, notes *string, hiddenFromStatusLists *bool, startedAt *FuzzyDateInput, completedAt *FuzzyDateInput, interceptors ...clientv2.RequestInterceptor) (*UpdateEntry, error)
|
||||
UpdateMediaListEntry(ctx context.Context, mediaID *int, status *MediaListStatus, scoreRaw *int, progress *int, startedAt *FuzzyDateInput, completedAt *FuzzyDateInput, interceptors ...clientv2.RequestInterceptor) (*UpdateMediaListEntry, error)
|
||||
UpdateMediaListEntryProgress(ctx context.Context, mediaID *int, progress *int, totalEpisodes *int) error
|
||||
UpdateMediaListEntryStatus(ctx context.Context, mediaID *int, progress *int, status *MediaListStatus, scoreRaw *int, interceptors ...clientv2.RequestInterceptor) (*UpdateMediaListEntryStatus, error)
|
||||
DeleteEntry(ctx context.Context, mediaListEntryID *int, interceptors ...clientv2.RequestInterceptor) (*DeleteEntry, error)
|
||||
AnimeCollection(ctx context.Context, userName *string, interceptors ...clientv2.RequestInterceptor) (*AnimeCollection, error)
|
||||
SearchAnimeShortMedia(ctx context.Context, page *int, perPage *int, sort []*MediaSort, search *string, status []*MediaStatus, interceptors ...clientv2.RequestInterceptor) (*SearchAnimeShortMedia, error)
|
||||
BasicMediaByMalID(ctx context.Context, id *int, interceptors ...clientv2.RequestInterceptor) (*BasicMediaByMalID, error)
|
||||
BasicMediaByID(ctx context.Context, id *int, interceptors ...clientv2.RequestInterceptor) (*BasicMediaByID, error)
|
||||
BaseMediaByID(ctx context.Context, id *int, interceptors ...clientv2.RequestInterceptor) (*BaseMediaByID, error)
|
||||
MediaDetailsByID(ctx context.Context, id *int, interceptors ...clientv2.RequestInterceptor) (*MediaDetailsByID, error)
|
||||
CompleteMediaByID(ctx context.Context, id *int, interceptors ...clientv2.RequestInterceptor) (*CompleteMediaByID, error)
|
||||
ListMedia(ctx context.Context, page *int, search *string, perPage *int, sort []*MediaSort, status []*MediaStatus, genres []*string, averageScoreGreater *int, season *MediaSeason, seasonYear *int, format *MediaFormat, interceptors ...clientv2.RequestInterceptor) (*ListMedia, error)
|
||||
ListRecentMedia(ctx context.Context, page *int, perPage *int, airingAtGreater *int, airingAtLesser *int, interceptors ...clientv2.RequestInterceptor) (*ListRecentMedia, error)
|
||||
GetViewer(ctx context.Context, interceptors ...clientv2.RequestInterceptor) (*GetViewer, error)
|
||||
AddMediaToPlanning(mIds []int, rateLimiter *limiter.Limiter, logger *zerolog.Logger) error
|
||||
}
|
||||
|
||||
type (
|
||||
// ClientWrapper is a wrapper around the AniList API client.
|
||||
ClientWrapper struct {
|
||||
@@ -47,6 +68,92 @@ func NewClientWrapper(token string) *ClientWrapper {
|
||||
return cw
|
||||
}
|
||||
|
||||
func (cw *ClientWrapper) AddMediaToPlanning(mIds []int, rateLimiter *limiter.Limiter, logger *zerolog.Logger) error {
|
||||
if len(mIds) == 0 {
|
||||
logger.Debug().Msg("anilist: no media added to planning list")
|
||||
return nil
|
||||
}
|
||||
if rateLimiter == nil {
|
||||
return errors.New("anilist: no rate limiter provided")
|
||||
}
|
||||
|
||||
status := MediaListStatusPlanning
|
||||
|
||||
scoreRaw := 0
|
||||
progress := 0
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
for _, _id := range mIds {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
rateLimiter.Wait()
|
||||
defer wg.Done()
|
||||
_, err := cw.Client.UpdateMediaListEntry(
|
||||
context.Background(),
|
||||
&id,
|
||||
&status,
|
||||
&scoreRaw,
|
||||
&progress,
|
||||
nil,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error().Msg("anilist: An error occurred while adding media to planning list: " + err.Error())
|
||||
}
|
||||
}(_id)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
logger.Debug().Any("count", len(mIds)).Msg("anilist: Media added to planning list")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cw *ClientWrapper) UpdateEntry(ctx context.Context, mediaID *int, status *MediaListStatus, score *float64, progress *int, repeat *int, private *bool, notes *string, hiddenFromStatusLists *bool, startedAt *FuzzyDateInput, completedAt *FuzzyDateInput, interceptors ...clientv2.RequestInterceptor) (*UpdateEntry, error) {
|
||||
return cw.Client.UpdateEntry(ctx, mediaID, status, score, progress, repeat, private, notes, hiddenFromStatusLists, startedAt, completedAt, interceptors...)
|
||||
}
|
||||
func (cw *ClientWrapper) UpdateMediaListEntry(ctx context.Context, mediaID *int, status *MediaListStatus, scoreRaw *int, progress *int, startedAt *FuzzyDateInput, completedAt *FuzzyDateInput, interceptors ...clientv2.RequestInterceptor) (*UpdateMediaListEntry, error) {
|
||||
return cw.Client.UpdateMediaListEntry(ctx, mediaID, status, scoreRaw, progress, startedAt, completedAt, interceptors...)
|
||||
}
|
||||
func (cw *ClientWrapper) UpdateMediaListEntryStatus(ctx context.Context, mediaID *int, progress *int, status *MediaListStatus, scoreRaw *int, interceptors ...clientv2.RequestInterceptor) (*UpdateMediaListEntryStatus, error) {
|
||||
return cw.Client.UpdateMediaListEntryStatus(ctx, mediaID, progress, status, scoreRaw, interceptors...)
|
||||
}
|
||||
func (cw *ClientWrapper) DeleteEntry(ctx context.Context, mediaListEntryID *int, interceptors ...clientv2.RequestInterceptor) (*DeleteEntry, error) {
|
||||
return cw.Client.DeleteEntry(ctx, mediaListEntryID, interceptors...)
|
||||
}
|
||||
func (cw *ClientWrapper) AnimeCollection(ctx context.Context, userName *string, interceptors ...clientv2.RequestInterceptor) (*AnimeCollection, error) {
|
||||
cw.logger.Trace().Str("username", *userName).Msg("anilist: Fetching anime collection")
|
||||
return cw.Client.AnimeCollection(ctx, userName, interceptors...)
|
||||
}
|
||||
func (cw *ClientWrapper) SearchAnimeShortMedia(ctx context.Context, page *int, perPage *int, sort []*MediaSort, search *string, status []*MediaStatus, interceptors ...clientv2.RequestInterceptor) (*SearchAnimeShortMedia, error) {
|
||||
return cw.Client.SearchAnimeShortMedia(ctx, page, perPage, sort, search, status, interceptors...)
|
||||
}
|
||||
func (cw *ClientWrapper) BasicMediaByMalID(ctx context.Context, id *int, interceptors ...clientv2.RequestInterceptor) (*BasicMediaByMalID, error) {
|
||||
return cw.Client.BasicMediaByMalID(ctx, id, interceptors...)
|
||||
}
|
||||
func (cw *ClientWrapper) BasicMediaByID(ctx context.Context, id *int, interceptors ...clientv2.RequestInterceptor) (*BasicMediaByID, error) {
|
||||
return cw.Client.BasicMediaByID(ctx, id, interceptors...)
|
||||
}
|
||||
func (cw *ClientWrapper) BaseMediaByID(ctx context.Context, id *int, interceptors ...clientv2.RequestInterceptor) (*BaseMediaByID, error) {
|
||||
cw.logger.Trace().Int("mediaId", *id).Msg("anilist: Fetching base media by ID")
|
||||
return cw.Client.BaseMediaByID(ctx, id, interceptors...)
|
||||
}
|
||||
func (cw *ClientWrapper) MediaDetailsByID(ctx context.Context, id *int, interceptors ...clientv2.RequestInterceptor) (*MediaDetailsByID, error) {
|
||||
return cw.Client.MediaDetailsByID(ctx, id, interceptors...)
|
||||
}
|
||||
func (cw *ClientWrapper) CompleteMediaByID(ctx context.Context, id *int, interceptors ...clientv2.RequestInterceptor) (*CompleteMediaByID, error) {
|
||||
return cw.Client.CompleteMediaByID(ctx, id, interceptors...)
|
||||
}
|
||||
func (cw *ClientWrapper) ListMedia(ctx context.Context, page *int, search *string, perPage *int, sort []*MediaSort, status []*MediaStatus, genres []*string, averageScoreGreater *int, season *MediaSeason, seasonYear *int, format *MediaFormat, interceptors ...clientv2.RequestInterceptor) (*ListMedia, error) {
|
||||
return cw.Client.ListMedia(ctx, page, search, perPage, sort, status, genres, averageScoreGreater, season, seasonYear, format, interceptors...)
|
||||
}
|
||||
func (cw *ClientWrapper) ListRecentMedia(ctx context.Context, page *int, perPage *int, airingAtGreater *int, airingAtLesser *int, interceptors ...clientv2.RequestInterceptor) (*ListRecentMedia, error) {
|
||||
return cw.Client.ListRecentMedia(ctx, page, perPage, airingAtGreater, airingAtLesser, interceptors...)
|
||||
}
|
||||
func (cw *ClientWrapper) GetViewer(ctx context.Context, interceptors ...clientv2.RequestInterceptor) (*GetViewer, error) {
|
||||
return cw.Client.GetViewer(ctx, interceptors...)
|
||||
}
|
||||
|
||||
// customDoFunc is a custom request interceptor function that handles rate limiting and retries.
|
||||
func (cw *ClientWrapper) customDoFunc(ctx context.Context, req *http.Request, gqlInfo *clientv2.GQLRequestInfo, res interface{}) (err error) {
|
||||
var rlRemainingStr string
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
func TestGetBaseMediaById(t *testing.T) {
|
||||
test_utils.InitTestProvider(t, test_utils.Anilist())
|
||||
|
||||
// Get Anilist client
|
||||
acw := TestGetAnilistClientWrapper()
|
||||
//acw := TestGetAnilistClientWrapper()
|
||||
acw := TestGetMockAnilistClientWrapper() // MockClientWrapper
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -25,7 +25,7 @@ func TestGetBaseMediaById(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
res, err := acw.Client.BaseMediaByID(context.Background(), &test.mediaId)
|
||||
res, err := acw.BaseMediaByID(context.Background(), &test.mediaId)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, res)
|
||||
})
|
||||
|
||||
@@ -13,7 +13,7 @@ func TestAddMediaToPlanning(t *testing.T) {
|
||||
|
||||
anilistClientWrapper := TestGetAnilistClientWrapper()
|
||||
|
||||
err := anilistClientWrapper.Client.AddMediaToPlanning(
|
||||
err := anilistClientWrapper.AddMediaToPlanning(
|
||||
[]int{131586},
|
||||
limiter.NewAnilistLimiter(),
|
||||
util.NewLogger(),
|
||||
|
||||
@@ -17,8 +17,8 @@ func NewBaseMediaCache() *BaseMediaCache {
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
func GetBaseMediaById(anilistClient *Client, id int) (*BaseMedia, error) {
|
||||
res, err := anilistClient.BaseMediaByID(context.Background(), &id)
|
||||
func GetBaseMediaById(acw ClientWrapperInterface, id int) (*BaseMedia, error) {
|
||||
res, err := acw.BaseMediaByID(context.Background(), &id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -28,9 +28,9 @@ func NewBaseMediaRelationTree() *BaseMediaRelationTree {
|
||||
return &BaseMediaRelationTree{result.NewResultMap[int, *BaseMedia]()}
|
||||
}
|
||||
|
||||
func (m *BasicMedia) FetchMediaTree(rel FetchMediaTreeRelation, acw *ClientWrapper, rl *limiter.Limiter, tree *BaseMediaRelationTree, cache *BaseMediaCache) error {
|
||||
func (m *BasicMedia) FetchMediaTree(rel FetchMediaTreeRelation, acw ClientWrapperInterface, rl *limiter.Limiter, tree *BaseMediaRelationTree, cache *BaseMediaCache) error {
|
||||
rl.Wait()
|
||||
res, err := acw.Client.BaseMediaByID(context.Background(), &m.ID)
|
||||
res, err := acw.BaseMediaByID(context.Background(), &m.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -40,7 +40,7 @@ func (m *BasicMedia) FetchMediaTree(rel FetchMediaTreeRelation, acw *ClientWrapp
|
||||
// FetchMediaTree populates the BaseMediaRelationTree with the given media's sequels and prequels.
|
||||
// It also takes a BaseMediaCache to store the fetched media in and avoid duplicate fetches.
|
||||
// It also takes a limiter.Limiter to limit the number of requests made to the AniList API.
|
||||
func (m *BaseMedia) FetchMediaTree(rel FetchMediaTreeRelation, acw *ClientWrapper, rl *limiter.Limiter, tree *BaseMediaRelationTree, cache *BaseMediaCache) error {
|
||||
func (m *BaseMedia) FetchMediaTree(rel FetchMediaTreeRelation, acw ClientWrapperInterface, rl *limiter.Limiter, tree *BaseMediaRelationTree, cache *BaseMediaCache) error {
|
||||
if tree.Has(m.ID) {
|
||||
cache.Set(m.ID, m)
|
||||
return nil
|
||||
@@ -78,7 +78,7 @@ func (m *BaseMedia) FetchMediaTree(rel FetchMediaTreeRelation, acw *ClientWrappe
|
||||
}
|
||||
|
||||
// processEdges fetches the next node(s) for each edge in parallel.
|
||||
func processEdges(edges []*BaseMedia_Relations_Edges, rel FetchMediaTreeRelation, acw *ClientWrapper, rl *limiter.Limiter, tree *BaseMediaRelationTree, cache *BaseMediaCache, doneCh chan struct{}) {
|
||||
func processEdges(edges []*BaseMedia_Relations_Edges, rel FetchMediaTreeRelation, acw ClientWrapperInterface, rl *limiter.Limiter, tree *BaseMediaRelationTree, cache *BaseMediaCache, doneCh chan struct{}) {
|
||||
lop.ForEach(edges, func(edge *BaseMedia_Relations_Edges, _ int) {
|
||||
processEdge(edge, rel, acw, rl, tree, cache)
|
||||
})
|
||||
@@ -87,13 +87,13 @@ func processEdges(edges []*BaseMedia_Relations_Edges, rel FetchMediaTreeRelation
|
||||
}()
|
||||
}
|
||||
|
||||
func processEdge(edge *BaseMedia_Relations_Edges, rel FetchMediaTreeRelation, acw *ClientWrapper, rl *limiter.Limiter, tree *BaseMediaRelationTree, cache *BaseMediaCache) {
|
||||
func processEdge(edge *BaseMedia_Relations_Edges, rel FetchMediaTreeRelation, acw ClientWrapperInterface, rl *limiter.Limiter, tree *BaseMediaRelationTree, cache *BaseMediaCache) {
|
||||
cacheV, ok := cache.Get(edge.GetNode().ID)
|
||||
edgeBaseMedia := cacheV
|
||||
if !ok {
|
||||
rl.Wait()
|
||||
// Fetch the next node
|
||||
res, err := acw.Client.BaseMediaByID(context.Background(), &edge.GetNode().ID)
|
||||
res, err := acw.BaseMediaByID(context.Background(), &edge.GetNode().ID)
|
||||
if err == nil {
|
||||
edgeBaseMedia = res.GetMedia()
|
||||
cache.Set(edgeBaseMedia.ID, edgeBaseMedia)
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
func TestBaseMedia_FetchMediaTree(t *testing.T) {
|
||||
test_utils.InitTestProvider(t, test_utils.Anilist())
|
||||
|
||||
acw := TestGetAnilistClientWrapper()
|
||||
acw := TestGetMockAnilistClientWrapper()
|
||||
lim := limiter.NewAnilistLimiter()
|
||||
baseMediaCache := NewBaseMediaCache()
|
||||
|
||||
@@ -38,7 +38,7 @@ func TestBaseMedia_FetchMediaTree(t *testing.T) {
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
mediaF, err := acw.Client.BaseMediaByID(context.Background(), &tt.mediaId)
|
||||
mediaF, err := acw.BaseMediaByID(context.Background(), &tt.mediaId)
|
||||
|
||||
if assert.NoError(t, err) {
|
||||
|
||||
@@ -99,7 +99,7 @@ func TestBasicMedia_FetchMediaTree(t *testing.T) {
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
mediaF, err := acw.Client.BasicMediaByID(context.Background(), &tt.mediaId)
|
||||
mediaF, err := acw.BasicMediaByID(context.Background(), &tt.mediaId)
|
||||
|
||||
if assert.NoError(t, err) {
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
package anilist
|
||||
|
||||
import (
|
||||
"github.com/seanime-app/seanime/internal/test_utils"
|
||||
)
|
||||
|
||||
// This file contains helper functions for testing the anilist package
|
||||
|
||||
func TestGetAnilistClientWrapper() *ClientWrapper {
|
||||
return NewClientWrapper(test_utils.ConfigData.Provider.AnilistJwt)
|
||||
}
|
||||
|
||||
func TestGetAnilistClientWrapperAndInfo() (*ClientWrapper, *test_utils.Config) {
|
||||
cw := NewClientWrapper(test_utils.ConfigData.Provider.AnilistJwt)
|
||||
return cw, test_utils.ConfigData
|
||||
}
|
||||
369
internal/anilist/test_helpers.go
Normal file
369
internal/anilist/test_helpers.go
Normal file
@@ -0,0 +1,369 @@
|
||||
package anilist
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/Yamashou/gqlgenc/clientv2"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/seanime-app/seanime/internal/limiter"
|
||||
"github.com/seanime-app/seanime/internal/test_utils"
|
||||
"github.com/seanime-app/seanime/internal/util"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
// This file contains helper functions for testing the anilist package
|
||||
|
||||
// TestGetAnilistClientWrapper TESTING PURPOSES ONLY
|
||||
func TestGetAnilistClientWrapper() ClientWrapperInterface {
|
||||
return NewClientWrapper(test_utils.ConfigData.Provider.AnilistJwt)
|
||||
}
|
||||
|
||||
func TestGetMockAnilistClientWrapper() ClientWrapperInterface {
|
||||
return NewMockClientWrapper()
|
||||
}
|
||||
|
||||
// MockClientWrapper is a mock implementation of the ClientWrapperInterface, used for tests.
|
||||
// It uses the real implementation of the ClientWrapperInterface to make requests then populates a cache with the results.
|
||||
// This is to avoid making repeated requests to the AniList API during tests but still have realistic data.
|
||||
type MockClientWrapper struct {
|
||||
realClientWrapper ClientWrapperInterface
|
||||
logger *zerolog.Logger
|
||||
}
|
||||
|
||||
func NewMockClientWrapper() *MockClientWrapper {
|
||||
return &MockClientWrapper{
|
||||
realClientWrapper: NewClientWrapper(test_utils.ConfigData.Provider.AnilistJwt),
|
||||
logger: util.NewLogger(),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *MockClientWrapper) BasicMediaByMalID(ctx context.Context, id *int, interceptors ...clientv2.RequestInterceptor) (*BasicMediaByMalID, error) {
|
||||
file, err := os.Open(test_utils.GetTestDataPath("BasicMediaByMalID"))
|
||||
defer file.Close()
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
c.logger.Warn().Msgf("MockClientWrapper: CACHE MISS [BasicMediaByMalID]: %d", *id)
|
||||
ret, err := c.realClientWrapper.BasicMediaByMalID(context.Background(), id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := json.Marshal([]*BasicMediaByMalID{ret})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = os.WriteFile(test_utils.GetTestDataPath("BasicMediaByMalID"), data, 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
}
|
||||
|
||||
var media []*BasicMediaByMalID
|
||||
err = json.NewDecoder(file).Decode(&media)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
var ret *BasicMediaByMalID
|
||||
for _, m := range media {
|
||||
if m.GetMedia().ID == *id {
|
||||
ret = m
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ret == nil {
|
||||
c.logger.Warn().Msgf("MockClientWrapper: CACHE MISS [BasicMediaByMalID]: %d", *id)
|
||||
ret, err := c.realClientWrapper.BasicMediaByMalID(context.Background(), id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
media = append(media, ret)
|
||||
data, err := json.Marshal(media)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = os.WriteFile(test_utils.GetTestDataPath("BasicMediaByMalID"), data, 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
c.logger.Trace().Msgf("MockClientWrapper: CACHE HIT [BasicMediaByMalID]: %d", *id)
|
||||
return ret, nil
|
||||
}
|
||||
func (c *MockClientWrapper) BasicMediaByID(ctx context.Context, id *int, interceptors ...clientv2.RequestInterceptor) (*BasicMediaByID, error) {
|
||||
file, err := os.Open(test_utils.GetTestDataPath("BasicMediaByID"))
|
||||
defer file.Close()
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
c.logger.Warn().Msgf("MockClientWrapper: CACHE MISS [BasicMediaByID]: %d", *id)
|
||||
ret, err := c.realClientWrapper.BasicMediaByID(context.Background(), id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := json.Marshal([]*BasicMediaByID{ret})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = os.WriteFile(test_utils.GetTestDataPath("BasicMediaByID"), data, 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
}
|
||||
|
||||
var media []*BasicMediaByID
|
||||
err = json.NewDecoder(file).Decode(&media)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
var ret *BasicMediaByID
|
||||
for _, m := range media {
|
||||
if m.GetMedia().ID == *id {
|
||||
ret = m
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ret == nil {
|
||||
c.logger.Warn().Msgf("MockClientWrapper: CACHE MISS [BasicMediaByID]: %d", *id)
|
||||
ret, err := c.realClientWrapper.BasicMediaByID(context.Background(), id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
media = append(media, ret)
|
||||
data, err := json.Marshal(media)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = os.WriteFile(test_utils.GetTestDataPath("BasicMediaByID"), data, 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
c.logger.Trace().Msgf("MockClientWrapper: CACHE HIT [BasicMediaByID]: %d", *id)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (c *MockClientWrapper) BaseMediaByID(ctx context.Context, id *int, interceptors ...clientv2.RequestInterceptor) (*BaseMediaByID, error) {
|
||||
file, err := os.Open(test_utils.GetTestDataPath("BaseMediaByID"))
|
||||
defer file.Close()
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
c.logger.Warn().Msgf("MockClientWrapper: CACHE MISS [BaseMediaByID]: %d", *id)
|
||||
baseMedia, err := c.realClientWrapper.BaseMediaByID(context.Background(), id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := json.Marshal([]*BaseMediaByID{baseMedia})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = os.WriteFile(test_utils.GetTestDataPath("BaseMediaByID"), data, 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return baseMedia, nil
|
||||
}
|
||||
}
|
||||
|
||||
var media []*BaseMediaByID
|
||||
err = json.NewDecoder(file).Decode(&media)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
var baseMedia *BaseMediaByID
|
||||
for _, m := range media {
|
||||
if m.GetMedia().ID == *id {
|
||||
baseMedia = m
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if baseMedia == nil {
|
||||
c.logger.Warn().Msgf("MockClientWrapper: CACHE MISS [BaseMediaByID]: %d", *id)
|
||||
baseMedia, err := c.realClientWrapper.BaseMediaByID(context.Background(), id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
media = append(media, baseMedia)
|
||||
data, err := json.Marshal(media)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = os.WriteFile(test_utils.GetTestDataPath("BaseMediaByID"), data, 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return baseMedia, nil
|
||||
}
|
||||
|
||||
c.logger.Trace().Msgf("MockClientWrapper: CACHE HIT [BaseMediaByID]: %d", *id)
|
||||
return baseMedia, nil
|
||||
}
|
||||
|
||||
func (c *MockClientWrapper) AnimeCollection(ctx context.Context, userName *string, interceptors ...clientv2.RequestInterceptor) (*AnimeCollection, error) {
|
||||
file, err := os.Open(test_utils.GetTestDataPath("AnimeCollection"))
|
||||
defer file.Close()
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
c.logger.Warn().Msgf("MockClientWrapper: CACHE MISS [AnimeCollection]: %s", *userName)
|
||||
ret, err := c.realClientWrapper.AnimeCollection(context.Background(), userName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := json.Marshal(ret)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = os.WriteFile(test_utils.GetTestDataPath("AnimeCollection"), data, 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
}
|
||||
|
||||
var ret *AnimeCollection
|
||||
err = json.NewDecoder(file).Decode(&ret)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if ret == nil {
|
||||
c.logger.Warn().Msgf("MockClientWrapper: CACHE MISS [AnimeCollection]: %s", *userName)
|
||||
ret, err := c.realClientWrapper.AnimeCollection(context.Background(), userName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err := json.Marshal(ret)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = os.WriteFile(test_utils.GetTestDataPath("AnimeCollection"), data, 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
c.logger.Trace().Msgf("MockClientWrapper: CACHE HIT [AnimeCollection]: %s", *userName)
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
type TestModifyAnimeCollectionEntryInput struct {
|
||||
Status *MediaListStatus
|
||||
Progress *int
|
||||
Score *float64
|
||||
Episodes *int
|
||||
NextAiringEpisode *BaseMedia_NextAiringEpisode
|
||||
}
|
||||
|
||||
// TestModifyAnimeCollectionEntry will modify an entry in the fetched anime collection.
|
||||
// This is used to fine-tune the anime collection for testing purposes.
|
||||
//
|
||||
// Example: Setting a specific progress in case the origin anime collection has no progress
|
||||
func TestModifyAnimeCollectionEntry(ac *AnimeCollection, mId int, input TestModifyAnimeCollectionEntryInput) *AnimeCollection {
|
||||
if ac == nil {
|
||||
panic("AnimeCollection is nil")
|
||||
}
|
||||
|
||||
lists := ac.GetMediaListCollection().GetLists()
|
||||
|
||||
removedFromList := false
|
||||
var rEntry *AnimeCollection_MediaListCollection_Lists_Entries
|
||||
|
||||
if input.Status != nil {
|
||||
for _, list := range lists {
|
||||
entries := list.GetEntries()
|
||||
for idx, entry := range entries {
|
||||
if entry.GetMedia().ID == mId {
|
||||
if *list.Status != *input.Status {
|
||||
removedFromList = true
|
||||
entries = append(entries[:idx], entries[idx+1:]...)
|
||||
rEntry = entry
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if removedFromList {
|
||||
for _, list := range lists {
|
||||
if *list.Status == *input.Status {
|
||||
list.Entries = append(list.Entries, rEntry)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out:
|
||||
for _, list := range lists {
|
||||
entries := list.GetEntries()
|
||||
for _, entry := range entries {
|
||||
if entry.GetMedia().ID == mId {
|
||||
if input.Progress != nil {
|
||||
entry.Progress = input.Progress
|
||||
}
|
||||
if input.Score != nil {
|
||||
entry.Score = input.Score
|
||||
}
|
||||
if input.Episodes != nil {
|
||||
entry.Media.Episodes = input.Episodes
|
||||
}
|
||||
if input.NextAiringEpisode != nil {
|
||||
entry.Media.NextAiringEpisode = input.NextAiringEpisode
|
||||
}
|
||||
break out
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ac
|
||||
}
|
||||
|
||||
//
|
||||
// WILL NOT IMPLEMENT
|
||||
//
|
||||
|
||||
func (c *MockClientWrapper) AddMediaToPlanning(mIds []int, rateLimiter *limiter.Limiter, logger *zerolog.Logger) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (c *MockClientWrapper) UpdateMediaListEntryProgress(ctx context.Context, mediaID *int, progress *int, totalEpisodes *int) error {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (c *MockClientWrapper) UpdateEntry(ctx context.Context, mediaID *int, status *MediaListStatus, score *float64, progress *int, repeat *int, private *bool, notes *string, hiddenFromStatusLists *bool, startedAt *FuzzyDateInput, completedAt *FuzzyDateInput, interceptors ...clientv2.RequestInterceptor) (*UpdateEntry, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (c *MockClientWrapper) UpdateMediaListEntry(ctx context.Context, mediaID *int, status *MediaListStatus, scoreRaw *int, progress *int, startedAt *FuzzyDateInput, completedAt *FuzzyDateInput, interceptors ...clientv2.RequestInterceptor) (*UpdateMediaListEntry, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (c *MockClientWrapper) UpdateMediaListEntryStatus(ctx context.Context, mediaID *int, progress *int, status *MediaListStatus, scoreRaw *int, interceptors ...clientv2.RequestInterceptor) (*UpdateMediaListEntryStatus, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (c *MockClientWrapper) DeleteEntry(ctx context.Context, mediaListEntryID *int, interceptors ...clientv2.RequestInterceptor) (*DeleteEntry, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (c *MockClientWrapper) SearchAnimeShortMedia(ctx context.Context, page *int, perPage *int, sort []*MediaSort, search *string, status []*MediaStatus, interceptors ...clientv2.RequestInterceptor) (*SearchAnimeShortMedia, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (c *MockClientWrapper) MediaDetailsByID(ctx context.Context, id *int, interceptors ...clientv2.RequestInterceptor) (*MediaDetailsByID, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (c *MockClientWrapper) CompleteMediaByID(ctx context.Context, id *int, interceptors ...clientv2.RequestInterceptor) (*CompleteMediaByID, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (c *MockClientWrapper) ListMedia(ctx context.Context, page *int, search *string, perPage *int, sort []*MediaSort, status []*MediaStatus, genres []*string, averageScoreGreater *int, season *MediaSeason, seasonYear *int, format *MediaFormat, interceptors ...clientv2.RequestInterceptor) (*ListMedia, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (c *MockClientWrapper) ListRecentMedia(ctx context.Context, page *int, perPage *int, airingAtGreater *int, airingAtLesser *int, interceptors ...clientv2.RequestInterceptor) (*ListRecentMedia, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
func (c *MockClientWrapper) GetViewer(ctx context.Context, interceptors ...clientv2.RequestInterceptor) (*GetViewer, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
@@ -52,7 +52,7 @@ func TestSearchQuery(t *testing.T) {
|
||||
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
|
||||
mediaRes, err := anilistClientWrapper.Client.BaseMediaByID(context.Background(), &test.mId)
|
||||
mediaRes, err := anilistClientWrapper.BaseMediaByID(context.Background(), &test.mId)
|
||||
|
||||
if assert.NoError(t, err) {
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ type (
|
||||
TorrentClientRepository *torrent_client.Repository
|
||||
Watcher *scanner.Watcher
|
||||
AnizipCache *anizip.Cache // AnizipCache holds fetched AniZip media for 30 minutes. (used by route handlers)
|
||||
AnilistClientWrapper *anilist.ClientWrapper
|
||||
AnilistClientWrapper anilist.ClientWrapperInterface
|
||||
NyaaSearchCache *nyaa.SearchCache
|
||||
AnimeToshoSearchCache *animetosho.SearchCache
|
||||
anilistCollection *anilist.AnimeCollection
|
||||
|
||||
@@ -30,7 +30,7 @@ func (a *App) RefreshAnilistCollection() (*anilist.AnimeCollection, error) {
|
||||
}
|
||||
|
||||
// Else, get the collection from Anilist
|
||||
collection, err := a.AnilistClientWrapper.Client.AnimeCollection(context.Background(), &a.account.Username)
|
||||
collection, err := a.AnilistClientWrapper.AnimeCollection(context.Background(), &a.account.Username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"github.com/goccy/go-json"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func GetTestDatabaseInfo() *struct {
|
||||
DataDir string `json:"dataDir"`
|
||||
Name string `json:"name"`
|
||||
} {
|
||||
|
||||
path, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// Open the JSON file
|
||||
file, err := os.Open(filepath.Join(path, "../../test/db.json"))
|
||||
if err != nil {
|
||||
println("Error opening file:", err.Error())
|
||||
log.Fatal(err)
|
||||
return nil
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
jsonData, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
println("Error reading file:", err.Error())
|
||||
log.Fatal(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
var data *struct {
|
||||
DataDir string `json:"dataDir"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
if err := json.Unmarshal(jsonData, &data); err != nil {
|
||||
println("Error unmarshaling JSON:", err.Error())
|
||||
log.Fatal(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
}
|
||||
@@ -71,7 +71,7 @@ type (
|
||||
AnilistCollection *anilist.AnimeCollection
|
||||
LocalFiles []*LocalFile
|
||||
AnizipCache *anizip.Cache
|
||||
AnilistClientWrapper *anilist.ClientWrapper
|
||||
AnilistClientWrapper anilist.ClientWrapperInterface
|
||||
}
|
||||
)
|
||||
|
||||
@@ -258,7 +258,7 @@ func (lc *LibraryCollection) hydrateContinueWatchingList(
|
||||
localFiles []*LocalFile,
|
||||
anilistCollection *anilist.AnimeCollection,
|
||||
anizipCache *anizip.Cache,
|
||||
anilistClientWrapper *anilist.ClientWrapper,
|
||||
anilistClientWrapper anilist.ClientWrapperInterface,
|
||||
) {
|
||||
|
||||
// Get currently watching list
|
||||
|
||||
@@ -40,7 +40,7 @@ type (
|
||||
LocalFiles []*LocalFile // All local files
|
||||
AnizipCache *anizip.Cache
|
||||
AnilistCollection *anilist.AnimeCollection
|
||||
AnilistClientWrapper *anilist.ClientWrapper
|
||||
AnilistClientWrapper anilist.ClientWrapperInterface
|
||||
}
|
||||
)
|
||||
|
||||
@@ -82,7 +82,7 @@ func NewMediaEntry(opts *NewMediaEntryOptions) (*MediaEntry, error) {
|
||||
anilistEntry = &anilist.MediaListEntry{}
|
||||
|
||||
// Fetch the media
|
||||
fetchedMedia, err := anilist.GetBaseMediaById(opts.AnilistClientWrapper.Client, opts.MediaId) // DEVNOTE: Maybe cache it?
|
||||
fetchedMedia, err := anilist.GetBaseMediaById(opts.AnilistClientWrapper, opts.MediaId) // DEVNOTE: Maybe cache it?
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
131
internal/entities/media_entry_download_info_test.go
Normal file
131
internal/entities/media_entry_download_info_test.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/seanime-app/seanime/internal/anilist"
|
||||
"github.com/seanime-app/seanime/internal/anizip"
|
||||
"github.com/seanime-app/seanime/internal/test_utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewMediaEntryDownloadInfo(t *testing.T) {
|
||||
test_utils.InitTestProvider(t, test_utils.Anilist())
|
||||
|
||||
anilistClientWrapper := anilist.TestGetMockAnilistClientWrapper()
|
||||
anilistCollection, err := anilistClientWrapper.AnimeCollection(context.Background(), &test_utils.ConfigData.Provider.AnilistUsername)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
localFiles []*LocalFile
|
||||
mediaId int
|
||||
currentProgress int
|
||||
status anilist.MediaListStatus
|
||||
expectedEpisodeNumbersToDownload []struct {
|
||||
episodeNumber int
|
||||
aniDbEpisode string
|
||||
}
|
||||
}{
|
||||
{
|
||||
// AniList includes episode 0 as a main episode but AniDB lists it as a special S1
|
||||
// So we should expect to see episode 0 (S1) in the list of episodes to download
|
||||
name: "Mushoku Tensei: Jobless Reincarnation Season 2",
|
||||
localFiles: nil,
|
||||
mediaId: 146065,
|
||||
currentProgress: 0,
|
||||
status: anilist.MediaListStatusCurrent,
|
||||
expectedEpisodeNumbersToDownload: []struct {
|
||||
episodeNumber int
|
||||
aniDbEpisode string
|
||||
}{
|
||||
{episodeNumber: 0, aniDbEpisode: "S1"},
|
||||
{episodeNumber: 1, aniDbEpisode: "1"},
|
||||
{episodeNumber: 2, aniDbEpisode: "2"},
|
||||
{episodeNumber: 3, aniDbEpisode: "3"},
|
||||
{episodeNumber: 4, aniDbEpisode: "4"},
|
||||
{episodeNumber: 5, aniDbEpisode: "5"},
|
||||
{episodeNumber: 6, aniDbEpisode: "6"},
|
||||
{episodeNumber: 7, aniDbEpisode: "7"},
|
||||
{episodeNumber: 8, aniDbEpisode: "8"},
|
||||
{episodeNumber: 9, aniDbEpisode: "9"},
|
||||
{episodeNumber: 10, aniDbEpisode: "10"},
|
||||
{episodeNumber: 11, aniDbEpisode: "11"},
|
||||
{episodeNumber: 12, aniDbEpisode: "12"},
|
||||
},
|
||||
},
|
||||
{
|
||||
// Same as above but progress of 1 should just eliminate episode 0 from the list and not episode 1
|
||||
name: "Mushoku Tensei: Jobless Reincarnation Season 2 - 2",
|
||||
localFiles: nil,
|
||||
mediaId: 146065,
|
||||
currentProgress: 1,
|
||||
status: anilist.MediaListStatusCurrent,
|
||||
expectedEpisodeNumbersToDownload: []struct {
|
||||
episodeNumber int
|
||||
aniDbEpisode string
|
||||
}{
|
||||
{episodeNumber: 1, aniDbEpisode: "1"},
|
||||
{episodeNumber: 2, aniDbEpisode: "2"},
|
||||
{episodeNumber: 3, aniDbEpisode: "3"},
|
||||
{episodeNumber: 4, aniDbEpisode: "4"},
|
||||
{episodeNumber: 5, aniDbEpisode: "5"},
|
||||
{episodeNumber: 6, aniDbEpisode: "6"},
|
||||
{episodeNumber: 7, aniDbEpisode: "7"},
|
||||
{episodeNumber: 8, aniDbEpisode: "8"},
|
||||
{episodeNumber: 9, aniDbEpisode: "9"},
|
||||
{episodeNumber: 10, aniDbEpisode: "10"},
|
||||
{episodeNumber: 11, aniDbEpisode: "11"},
|
||||
{episodeNumber: 12, aniDbEpisode: "12"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
anizipCache := anizip.NewCache()
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
anizipData, err := anizip.FetchAniZipMediaC("anilist", tt.mediaId, anizipCache)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
anilistEntry, _ := anilistCollection.GetListEntryFromMediaId(tt.mediaId)
|
||||
|
||||
info, err := NewMediaEntryDownloadInfo(&NewMediaEntryDownloadInfoOptions{
|
||||
localFiles: tt.localFiles,
|
||||
anizipMedia: anizipData,
|
||||
progress: &tt.currentProgress,
|
||||
status: &tt.status,
|
||||
media: anilistEntry.Media,
|
||||
})
|
||||
|
||||
if assert.NoError(t, err) && assert.NotNil(t, info) {
|
||||
|
||||
foundEpToDownload := make([]struct {
|
||||
episodeNumber int
|
||||
aniDbEpisode string
|
||||
}, 0)
|
||||
for _, ep := range info.EpisodesToDownload {
|
||||
foundEpToDownload = append(foundEpToDownload, struct {
|
||||
episodeNumber int
|
||||
aniDbEpisode string
|
||||
}{
|
||||
episodeNumber: ep.EpisodeNumber,
|
||||
aniDbEpisode: ep.AniDBEpisode,
|
||||
})
|
||||
}
|
||||
|
||||
assert.ElementsMatch(t, tt.expectedEpisodeNumbersToDownload, foundEpToDownload)
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,12 +20,13 @@ type (
|
||||
// NewMediaEntryLibraryData creates a new MediaEntryLibraryData based on the media id and a list of local files related to the media.
|
||||
// It will return false if the list of local files is empty.
|
||||
func NewMediaEntryLibraryData(opts *NewMediaEntryLibraryDataOptions) (*MediaEntryLibraryData, bool) {
|
||||
|
||||
if opts.entryLocalFiles == nil || len(opts.entryLocalFiles) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
sharedPath := strings.Replace(opts.entryLocalFiles[0].Path, opts.entryLocalFiles[0].Name, "", 1)
|
||||
sharedPath, _ = strings.CutSuffix(sharedPath, "\\")
|
||||
sharedPath, _ = strings.CutSuffix(sharedPath, "/")
|
||||
sharedPath = strings.TrimSuffix(strings.TrimSuffix(sharedPath, "\\"), "/")
|
||||
|
||||
return &MediaEntryLibraryData{
|
||||
AllFilesLocked: lo.EveryBy(opts.entryLocalFiles, func(item *LocalFile) bool { return item.Locked }),
|
||||
SharedPath: sharedPath,
|
||||
|
||||
@@ -31,7 +31,7 @@ type (
|
||||
MediaId int
|
||||
LocalFiles []*LocalFile // All local files
|
||||
AnilistCollection *anilist.AnimeCollection
|
||||
AnilistClientWrapper *anilist.ClientWrapper
|
||||
AnilistClientWrapper anilist.ClientWrapperInterface
|
||||
}
|
||||
)
|
||||
|
||||
@@ -59,7 +59,7 @@ func NewSimpleMediaEntry(opts *NewSimpleMediaEntryOptions) (*SimpleMediaEntry, e
|
||||
anilistEntry = &anilist.MediaListEntry{}
|
||||
|
||||
// Fetch the media
|
||||
fetchedMedia, err := anilist.GetBaseMediaById(opts.AnilistClientWrapper.Client, opts.MediaId) // DEVNOTE: Maybe cache it?
|
||||
fetchedMedia, err := anilist.GetBaseMediaById(opts.AnilistClientWrapper, opts.MediaId) // DEVNOTE: Maybe cache it?
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1,214 +1,112 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"github.com/goccy/go-json"
|
||||
"context"
|
||||
"github.com/samber/lo"
|
||||
"github.com/seanime-app/seanime/internal/anilist"
|
||||
"github.com/seanime-app/seanime/internal/anizip"
|
||||
"github.com/seanime-app/seanime/internal/test_utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const mediaEntryMockDataFile = "./media_entry_test_mock_data.json"
|
||||
|
||||
// TestNewMediaEntry tests /library/entry endpoint.
|
||||
// /!\ MAKE SURE TO HAVE THE MEDIA ADDED TO YOUR LIST TEST ACCOUNT LISTS
|
||||
func TestNewMediaEntry(t *testing.T) {
|
||||
test_utils.InitTestProvider(t, test_utils.Anilist())
|
||||
|
||||
var mediaId = 154587 // Sousou no Frieren
|
||||
lfs, anilistCollection, err := getMediaEntryMockData(t, mediaId)
|
||||
|
||||
if assert.NoErrorf(t, err, "Failed to get mock data") &&
|
||||
assert.NotNil(t, lfs) &&
|
||||
assert.NotNil(t, anilistCollection) {
|
||||
|
||||
entry, err := NewMediaEntry(&NewMediaEntryOptions{
|
||||
MediaId: mediaId,
|
||||
LocalFiles: lfs,
|
||||
AnizipCache: anizip.NewCache(),
|
||||
AnilistCollection: anilistCollection,
|
||||
AnilistClientWrapper: anilist.TestGetAnilistClientWrapper(),
|
||||
})
|
||||
|
||||
if assert.NoError(t, err) {
|
||||
|
||||
assert.Equalf(t, len(lfs), len(entry.Episodes), "Number of episodes mismatch")
|
||||
|
||||
// Mock data progress is 4
|
||||
if assert.NotNilf(t, entry.NextEpisode, "Next episode not found") {
|
||||
assert.Equal(t, 5, entry.NextEpisode.EpisodeNumber, "Next episode mismatch")
|
||||
}
|
||||
|
||||
t.Logf("Success, found %v episodes", len(entry.Episodes))
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewMediaEntry2(t *testing.T) {
|
||||
|
||||
var mediaId = 146065 // Mushoku Tensei: Jobless Reincarnation Season 2
|
||||
_, anilistCollection, err := getMediaEntryMockData(t, mediaId)
|
||||
|
||||
var lfs []*LocalFile
|
||||
for idx, path := range []string{
|
||||
"E:/Anime/Mushoku Tensei II Isekai Ittara Honki Dasu/[SubsPlease] Mushoku Tensei S2 - 00 (1080p) [9C362DC3].mkv",
|
||||
"E:/Anime/Mushoku Tensei II Isekai Ittara Honki Dasu/[SubsPlease] Mushoku Tensei S2 - 01 (1080p) [EC64C8B1].mkv",
|
||||
"E:/Anime/Mushoku Tensei II Isekai Ittara Honki Dasu/[SubsPlease] Mushoku Tensei S2 - 02 (1080p) [7EA9E789].mkv",
|
||||
"E:/Anime/Mushoku Tensei II Isekai Ittara Honki Dasu/[SubsPlease] Mushoku Tensei S2 - 03 (1080p) [BEF3095D].mkv",
|
||||
"E:/Anime/Mushoku Tensei II Isekai Ittara Honki Dasu/[SubsPlease] Mushoku Tensei S2 - 04 (1080p) [FD2285EB].mkv",
|
||||
"E:/Anime/Mushoku Tensei II Isekai Ittara Honki Dasu/[SubsPlease] Mushoku Tensei S2 - 05 (1080p) [E691CDB3].mkv",
|
||||
"E:/Anime/Mushoku Tensei II Isekai Ittara Honki Dasu/[SubsPlease] Mushoku Tensei S2 - 06 (1080p) [0438103E].mkv",
|
||||
"E:/Anime/Mushoku Tensei II Isekai Ittara Honki Dasu/[SubsPlease] Mushoku Tensei S2 - 07 (1080p) [DA6366AD].mkv",
|
||||
"E:/Anime/Mushoku Tensei II Isekai Ittara Honki Dasu/[SubsPlease] Mushoku Tensei S2 - 08 (1080p) [A761377D].mkv",
|
||||
"E:/Anime/Mushoku Tensei II Isekai Ittara Honki Dasu/[SubsPlease] Mushoku Tensei S2 - 09 (1080p) [DFE9A041].mkv",
|
||||
"E:/Anime/Mushoku Tensei II Isekai Ittara Honki Dasu/[SubsPlease] Mushoku Tensei S2 - 10 (1080p) [DFE1B93B].mkv",
|
||||
"E:/Anime/Mushoku Tensei II Isekai Ittara Honki Dasu/[SubsPlease] Mushoku Tensei S2 - 11 (1080p) [F70DC34C].mkv",
|
||||
"E:/Anime/Mushoku Tensei II Isekai Ittara Honki Dasu/[SubsPlease] Mushoku Tensei S2 - 12 (1080p) [BAA0EBAD].mkv",
|
||||
} {
|
||||
lf := NewLocalFile(path, "E:/Anime")
|
||||
// Mock hydration
|
||||
lf.MediaId = mediaId
|
||||
lf.Metadata = &LocalFileMetadata{
|
||||
Type: LocalFileTypeMain,
|
||||
Episode: idx,
|
||||
AniDBEpisode: strconv.Itoa(idx),
|
||||
}
|
||||
if idx == 0 {
|
||||
lf.Metadata.AniDBEpisode = "S1"
|
||||
}
|
||||
lfs = append(lfs, lf)
|
||||
tests := []struct {
|
||||
name string
|
||||
mediaId int
|
||||
localFiles []*LocalFile
|
||||
currentProgress int
|
||||
expectedNextEpisodeNumber int
|
||||
expectedNextEpisodeProgressNumber int
|
||||
}{
|
||||
{
|
||||
name: "Sousou no Frieren",
|
||||
mediaId: 154587,
|
||||
localFiles: MockHydratedLocalFiles(
|
||||
MockGenerateHydratedLocalFileGroupOptions("E:/Anime", "E:\\Anime\\Sousou no Frieren\\[SubsPlease] Sousou no Frieren - %ep (1080p) [F02B9CEE].mkv", 154587, []MockHydratedLocalFileWrapperOptionsMetadata{
|
||||
{metadataEpisode: 1, metadataAniDbEpisode: "1", metadataType: LocalFileTypeMain},
|
||||
{metadataEpisode: 2, metadataAniDbEpisode: "2", metadataType: LocalFileTypeMain},
|
||||
{metadataEpisode: 3, metadataAniDbEpisode: "3", metadataType: LocalFileTypeMain},
|
||||
{metadataEpisode: 4, metadataAniDbEpisode: "4", metadataType: LocalFileTypeMain},
|
||||
{metadataEpisode: 5, metadataAniDbEpisode: "5", metadataType: LocalFileTypeMain},
|
||||
}),
|
||||
),
|
||||
currentProgress: 4,
|
||||
expectedNextEpisodeNumber: 5,
|
||||
expectedNextEpisodeProgressNumber: 5,
|
||||
},
|
||||
{
|
||||
name: "Mushoku Tensei II Isekai Ittara Honki Dasu",
|
||||
mediaId: 146065,
|
||||
localFiles: MockHydratedLocalFiles(
|
||||
MockGenerateHydratedLocalFileGroupOptions("E:/Anime", "E:/Anime/Mushoku Tensei II Isekai Ittara Honki Dasu/[SubsPlease] Mushoku Tensei S2 - 00 (1080p) [9C362DC3].mkv", 146065, []MockHydratedLocalFileWrapperOptionsMetadata{
|
||||
{metadataEpisode: 0, metadataAniDbEpisode: "S1", metadataType: LocalFileTypeMain}, // Special episode
|
||||
{metadataEpisode: 1, metadataAniDbEpisode: "1", metadataType: LocalFileTypeMain},
|
||||
{metadataEpisode: 2, metadataAniDbEpisode: "2", metadataType: LocalFileTypeMain},
|
||||
{metadataEpisode: 3, metadataAniDbEpisode: "3", metadataType: LocalFileTypeMain},
|
||||
{metadataEpisode: 4, metadataAniDbEpisode: "4", metadataType: LocalFileTypeMain},
|
||||
{metadataEpisode: 5, metadataAniDbEpisode: "5", metadataType: LocalFileTypeMain},
|
||||
{metadataEpisode: 6, metadataAniDbEpisode: "6", metadataType: LocalFileTypeMain},
|
||||
{metadataEpisode: 7, metadataAniDbEpisode: "7", metadataType: LocalFileTypeMain},
|
||||
{metadataEpisode: 8, metadataAniDbEpisode: "8", metadataType: LocalFileTypeMain},
|
||||
{metadataEpisode: 9, metadataAniDbEpisode: "9", metadataType: LocalFileTypeMain},
|
||||
{metadataEpisode: 10, metadataAniDbEpisode: "10", metadataType: LocalFileTypeMain},
|
||||
{metadataEpisode: 11, metadataAniDbEpisode: "11", metadataType: LocalFileTypeMain},
|
||||
{metadataEpisode: 12, metadataAniDbEpisode: "12", metadataType: LocalFileTypeMain},
|
||||
}),
|
||||
),
|
||||
currentProgress: 0,
|
||||
expectedNextEpisodeNumber: 0,
|
||||
expectedNextEpisodeProgressNumber: 1,
|
||||
},
|
||||
}
|
||||
|
||||
if assert.NoErrorf(t, err, "Failed to get mock data") &&
|
||||
assert.NotNil(t, lfs) &&
|
||||
assert.NotNil(t, anilistCollection) {
|
||||
|
||||
entry, err := NewMediaEntry(&NewMediaEntryOptions{
|
||||
MediaId: mediaId,
|
||||
LocalFiles: lfs,
|
||||
AnizipCache: anizip.NewCache(),
|
||||
AnilistCollection: anilistCollection,
|
||||
AnilistClientWrapper: anilist.TestGetAnilistClientWrapper(),
|
||||
})
|
||||
|
||||
if assert.NoError(t, err) {
|
||||
|
||||
assert.Equalf(t, len(lfs), len(entry.Episodes), "Number of episodes mismatch")
|
||||
|
||||
// Mock data progress is 0 so the next episode should be "0" (S1) with progress number 1
|
||||
if assert.NotNilf(t, entry.NextEpisode, "Next episode not found") {
|
||||
assert.Equal(t, 0, entry.NextEpisode.EpisodeNumber, "Next episode number mismatch")
|
||||
assert.Equal(t, 1, entry.NextEpisode.ProgressNumber, "Next episode progress number mismatch")
|
||||
}
|
||||
|
||||
t.Logf("Success, found %v episodes", len(entry.Episodes))
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewMissingEpisodes(t *testing.T) {
|
||||
|
||||
var mediaId = 154587 // Sousou no Frieren
|
||||
lfs, anilistCollection, err := getMediaEntryMockData(t, mediaId)
|
||||
|
||||
if assert.NoError(t, err) {
|
||||
missingData := NewMissingEpisodes(&NewMissingEpisodesOptions{
|
||||
AnilistCollection: anilistCollection,
|
||||
LocalFiles: lfs,
|
||||
AnizipCache: anizip.NewCache(),
|
||||
})
|
||||
|
||||
// Mock data has 5 files, Current number of episodes is 10, so 5 episodes are missing
|
||||
assert.Equal(t, 5, len(missingData.Episodes))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Fetching "Mushoku Tensei: Jobless Reincarnation Season 2" download info
|
||||
// Anilist: 13 episodes, Anizip: 12 episodes + "S1"
|
||||
// Info should include "S1" as episode 0
|
||||
func TestNewMediaEntryDownloadInfo(t *testing.T) {
|
||||
|
||||
var mediaId = 146065 // Mushoku Tensei: Jobless Reincarnation Season 2
|
||||
|
||||
_, anilistCollection, err := getMediaEntryMockData(t, mediaId)
|
||||
anilistClientWrapper := anilist.TestGetMockAnilistClientWrapper()
|
||||
anilistCollection, err := anilistClientWrapper.AnimeCollection(context.Background(), &test_utils.ConfigData.Provider.AnilistUsername)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
anizipData, err := anizip.FetchAniZipMedia("anilist", mediaId)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
aniZipCache := anizip.NewCache()
|
||||
|
||||
anilistEntry, ok := anilistCollection.GetListEntryFromMediaId(mediaId)
|
||||
for _, tt := range tests {
|
||||
|
||||
if assert.Truef(t, ok, "Could not find media entry for %d", mediaId) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
assert.Equal(t, 13, anilistEntry.Media.GetCurrentEpisodeCount(), "Number of episodes mismatch on Anilist")
|
||||
assert.Equal(t, 12, anizipData.GetMainEpisodeCount(), "Number of episodes mismatch on Anizip")
|
||||
|
||||
info, err := NewMediaEntryDownloadInfo(&NewMediaEntryDownloadInfoOptions{
|
||||
localFiles: nil,
|
||||
anizipMedia: anizipData,
|
||||
progress: lo.ToPtr(0),
|
||||
status: lo.ToPtr(anilist.MediaListStatusCurrent),
|
||||
media: anilistEntry.Media,
|
||||
})
|
||||
|
||||
if assert.NoError(t, err) && assert.NotNil(t, info) {
|
||||
|
||||
_, found := lo.Find(info.EpisodesToDownload, func(i *MediaEntryDownloadEpisode) bool {
|
||||
return i.EpisodeNumber == 0 && i.AniDBEpisode == "S1"
|
||||
// && i.Episode.ProgressNumber == 1 DEVNOTE: Progress numbers are always 0 because we don't have local files
|
||||
anilist.TestModifyAnimeCollectionEntry(anilistCollection, tt.mediaId, anilist.TestModifyAnimeCollectionEntryInput{
|
||||
Progress: lo.ToPtr(tt.currentProgress), // Mock progress
|
||||
})
|
||||
|
||||
assert.True(t, found)
|
||||
entry, err := NewMediaEntry(&NewMediaEntryOptions{
|
||||
MediaId: tt.mediaId,
|
||||
LocalFiles: tt.localFiles,
|
||||
AnizipCache: aniZipCache,
|
||||
AnilistCollection: anilistCollection,
|
||||
AnilistClientWrapper: anilistClientWrapper,
|
||||
})
|
||||
|
||||
}
|
||||
if assert.NoErrorf(t, err, "Failed to get mock data") {
|
||||
|
||||
if assert.NoError(t, err) {
|
||||
|
||||
// Mock progress is 4
|
||||
nextEp, found := entry.FindNextEpisode()
|
||||
if assert.True(t, found, "did not find next episode") {
|
||||
assert.Equal(t, tt.expectedNextEpisodeNumber, nextEp.EpisodeNumber, "next episode number mismatch")
|
||||
assert.Equal(t, tt.expectedNextEpisodeProgressNumber, nextEp.ProgressNumber, "next episode progress number mismatch")
|
||||
}
|
||||
|
||||
t.Logf("Found %v episodes", len(entry.Episodes))
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
func getMediaEntryMockData(t *testing.T, mediaId int) ([]*LocalFile, *anilist.AnimeCollection, error) {
|
||||
|
||||
path, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Open the JSON file
|
||||
file, err := os.Open(filepath.Join(path, mediaEntryMockDataFile))
|
||||
if err != nil {
|
||||
t.Fatal("Error opening file:", err.Error())
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
jsonData, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
t.Fatal("Error reading file:", err.Error())
|
||||
}
|
||||
|
||||
var data map[string]struct {
|
||||
LocalFiles []*LocalFile `json:"localFiles"`
|
||||
AnilistCollection *anilist.AnimeCollection `json:"anilistCollection"`
|
||||
}
|
||||
if err := json.Unmarshal(jsonData, &data); err != nil {
|
||||
t.Fatal("Error unmarshaling JSON:", err.Error())
|
||||
}
|
||||
|
||||
ret, _ := data[strconv.Itoa(mediaId)]
|
||||
|
||||
return ret.LocalFiles, ret.AnilistCollection, nil
|
||||
|
||||
}
|
||||
|
||||
79
internal/entities/missing_episodes_test.go
Normal file
79
internal/entities/missing_episodes_test.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/samber/lo"
|
||||
"github.com/seanime-app/seanime/internal/anilist"
|
||||
"github.com/seanime-app/seanime/internal/anizip"
|
||||
"github.com/seanime-app/seanime/internal/test_utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test to retrieve accurate missing episodes
|
||||
func TestNewMissingEpisodes(t *testing.T) {
|
||||
test_utils.InitTestProvider(t, test_utils.Anilist())
|
||||
|
||||
anilistClientWrapper := anilist.TestGetMockAnilistClientWrapper()
|
||||
anilistCollection, err := anilistClientWrapper.AnimeCollection(context.Background(), &test_utils.ConfigData.Provider.AnilistUsername)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
mediaId int
|
||||
localFiles []*LocalFile
|
||||
mediaAiredEpisodes int
|
||||
currentProgress int
|
||||
expectedMissingEpisodes int
|
||||
}{
|
||||
{
|
||||
// Sousou no Frieren - 10 currently aired episodes
|
||||
// User has 5 local files from ep 1 to 5, but only watched 4 episodes
|
||||
// So we should expect to see 5 missing episodes
|
||||
name: "Sousou no Frieren, missing 5 episodes",
|
||||
mediaId: 154587,
|
||||
localFiles: MockHydratedLocalFiles(
|
||||
MockGenerateHydratedLocalFileGroupOptions("E:/Anime", "E:\\Anime\\Sousou no Frieren\\[SubsPlease] Sousou no Frieren - %ep (1080p) [F02B9CEE].mkv", 154587, []MockHydratedLocalFileWrapperOptionsMetadata{
|
||||
{metadataEpisode: 1, metadataAniDbEpisode: "1", metadataType: LocalFileTypeMain},
|
||||
{metadataEpisode: 2, metadataAniDbEpisode: "2", metadataType: LocalFileTypeMain},
|
||||
{metadataEpisode: 3, metadataAniDbEpisode: "3", metadataType: LocalFileTypeMain},
|
||||
{metadataEpisode: 4, metadataAniDbEpisode: "4", metadataType: LocalFileTypeMain},
|
||||
{metadataEpisode: 5, metadataAniDbEpisode: "5", metadataType: LocalFileTypeMain},
|
||||
}),
|
||||
),
|
||||
mediaAiredEpisodes: 10,
|
||||
currentProgress: 4,
|
||||
expectedMissingEpisodes: 5,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
// Mock Anilist collection
|
||||
anilist.TestModifyAnimeCollectionEntry(anilistCollection, tt.mediaId, anilist.TestModifyAnimeCollectionEntryInput{
|
||||
Progress: lo.ToPtr(tt.currentProgress), // Mock progress
|
||||
Episodes: lo.ToPtr(tt.mediaAiredEpisodes), // Mock total episodes
|
||||
NextAiringEpisode: &anilist.BaseMedia_NextAiringEpisode{
|
||||
Episode: tt.mediaAiredEpisodes + 1,
|
||||
},
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
if assert.NoError(t, err) {
|
||||
missingData := NewMissingEpisodes(&NewMissingEpisodesOptions{
|
||||
AnilistCollection: anilistCollection,
|
||||
LocalFiles: tt.localFiles,
|
||||
AnizipCache: anizip.NewCache(),
|
||||
})
|
||||
|
||||
assert.Equal(t, tt.expectedMissingEpisodes, len(missingData.Episodes))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -36,7 +36,7 @@ func HandleEditAnilistListEntry(c *RouteCtx) error {
|
||||
return c.RespondWithError(err)
|
||||
}
|
||||
|
||||
ret, err := c.App.AnilistClientWrapper.Client.UpdateMediaListEntry(
|
||||
ret, err := c.App.AnilistClientWrapper.UpdateMediaListEntry(
|
||||
c.Fiber.Context(),
|
||||
p.MediaId,
|
||||
p.Status,
|
||||
@@ -103,7 +103,7 @@ func HandleGetAnilistMediaDetails(c *RouteCtx) error {
|
||||
if details, ok := detailsCache.Get(mId); ok {
|
||||
return c.RespondWithData(details)
|
||||
}
|
||||
details, err := c.App.AnilistClientWrapper.Client.MediaDetailsByID(c.Fiber.Context(), &mId)
|
||||
details, err := c.App.AnilistClientWrapper.MediaDetailsByID(c.Fiber.Context(), &mId)
|
||||
if err != nil {
|
||||
return c.RespondWithError(err)
|
||||
}
|
||||
@@ -137,7 +137,7 @@ func HandleDeleteAnilistListEntry(c *RouteCtx) error {
|
||||
}
|
||||
|
||||
// Delete the list entry
|
||||
ret, err := c.App.AnilistClientWrapper.Client.DeleteEntry(
|
||||
ret, err := c.App.AnilistClientWrapper.DeleteEntry(
|
||||
c.Fiber.Context(),
|
||||
&listEntry.ID,
|
||||
)
|
||||
|
||||
@@ -31,7 +31,7 @@ func HandleLogin(c *RouteCtx) error {
|
||||
c.App.UpdateAnilistClientToken(body.Token)
|
||||
|
||||
// Get viewer data from AniList
|
||||
getViewer, err := c.App.AnilistClientWrapper.Client.GetViewer(context.Background())
|
||||
getViewer, err := c.App.AnilistClientWrapper.GetViewer(context.Background())
|
||||
if err != nil {
|
||||
c.App.Logger.Error().Msg("Could not authenticate to AniList")
|
||||
return c.RespondWithError(err)
|
||||
|
||||
@@ -297,7 +297,7 @@ func HandleFindProspectiveMediaEntrySuggestions(c *RouteCtx) error {
|
||||
return media
|
||||
}
|
||||
// Otherwise, fetch the media
|
||||
mediaRes, err := c.App.AnilistClientWrapper.Client.BasicMediaByMalID(context.Background(), &s.ID)
|
||||
mediaRes, err := c.App.AnilistClientWrapper.BasicMediaByMalID(context.Background(), &s.ID)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
@@ -365,7 +365,7 @@ func HandleMediaEntryManualMatch(c *RouteCtx) error {
|
||||
})
|
||||
|
||||
// Get the media
|
||||
mediaRes, err := c.App.AnilistClientWrapper.Client.BaseMediaByID(context.Background(), &b.MediaId)
|
||||
mediaRes, err := c.App.AnilistClientWrapper.BaseMediaByID(context.Background(), &b.MediaId)
|
||||
if err != nil {
|
||||
return c.RespondWithError(err)
|
||||
}
|
||||
@@ -464,7 +464,7 @@ func HandleAddUnknownMedia(c *RouteCtx) error {
|
||||
}
|
||||
|
||||
// Add non-added media entries to AniList collection
|
||||
if err := c.App.AnilistClientWrapper.Client.AddMediaToPlanning(b.MediaIds, limiter.NewAnilistLimiter(), c.App.Logger); err != nil {
|
||||
if err := c.App.AnilistClientWrapper.AddMediaToPlanning(b.MediaIds, limiter.NewAnilistLimiter(), c.App.Logger); err != nil {
|
||||
return c.RespondWithError(errors.New("error: Anilist responded with an error, this is most likely a rate limit issue"))
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ type (
|
||||
AnimeEntriesMap map[int]*AnimeEntry
|
||||
}
|
||||
ProviderRepository struct { // Holds information used for making requests to the providers
|
||||
AnilistClientWrapper *anilist.ClientWrapper
|
||||
AnilistClientWrapper anilist.ClientWrapperInterface
|
||||
MalWrapper *mal.Wrapper
|
||||
Logger *zerolog.Logger
|
||||
}
|
||||
@@ -70,7 +70,7 @@ func (pr *ProviderRepository) AddAnime(to Source, entry *AnimeEntry) error {
|
||||
status := ToAnilistListStatus(entry.Status)
|
||||
score := entry.Score * 10
|
||||
|
||||
_, err = pr.AnilistClientWrapper.Client.UpdateMediaListEntryStatus(
|
||||
_, err = pr.AnilistClientWrapper.UpdateMediaListEntryStatus(
|
||||
context.Background(),
|
||||
&anilistId,
|
||||
&entry.Progress,
|
||||
@@ -129,7 +129,7 @@ func (pr *ProviderRepository) UpdateAnime(to Source, entry *AnimeEntry) error {
|
||||
status := ToAnilistListStatus(entry.Status)
|
||||
score := entry.Score * 10
|
||||
|
||||
_, err = pr.AnilistClientWrapper.Client.UpdateMediaListEntryStatus(
|
||||
_, err = pr.AnilistClientWrapper.UpdateMediaListEntryStatus(
|
||||
context.Background(),
|
||||
&anilistId,
|
||||
&entry.Progress,
|
||||
@@ -183,7 +183,7 @@ func (pr *ProviderRepository) DeleteAnime(from Source, entry *AnimeEntry) error
|
||||
return errors.New("anilist id not found")
|
||||
}
|
||||
|
||||
_, err = pr.AnilistClientWrapper.Client.DeleteEntry(
|
||||
_, err = pr.AnilistClientWrapper.DeleteEntry(
|
||||
context.Background(),
|
||||
&anilistId,
|
||||
)
|
||||
|
||||
@@ -66,7 +66,7 @@ func BuildListSync(db *db.Database, logger *zerolog.Logger) (ls *ListSync, err e
|
||||
|
||||
// Get Anilist collection
|
||||
anilistClientWrapper := anilist.NewClientWrapper(account.Token)
|
||||
collection, err := anilistClientWrapper.Client.AnimeCollection(context.Background(), &account.Username)
|
||||
collection, err := anilistClientWrapper.AnimeCollection(context.Background(), &account.Username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ func TestBuildSearchQuery(t *testing.T) {
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
media, err := anilist.GetBaseMediaById(anilistClientWrapper.Client, tt.mediaId)
|
||||
media, err := anilist.GetBaseMediaById(anilistClientWrapper, tt.mediaId)
|
||||
|
||||
if assert.NoError(t, err) &&
|
||||
assert.NotNil(t, media) {
|
||||
|
||||
@@ -28,7 +28,7 @@ type (
|
||||
MediaPlayerRepository *mediaplayer.Repository // MediaPlayerRepository is used to control the media player
|
||||
mediaPlayerRepoSubscriber *mediaplayer.RepositorySubscriber // Used to listen for media player events
|
||||
wsEventManager events.IWSEventManager
|
||||
anilistClientWrapper *anilist.ClientWrapper
|
||||
anilistClientWrapper anilist.ClientWrapperInterface
|
||||
anilistCollection *anilist.AnimeCollection
|
||||
refreshAnilistCollectionFunc func() // This function is called to refresh the AniList collection
|
||||
mu sync.Mutex
|
||||
@@ -62,7 +62,7 @@ type (
|
||||
NewProgressManagerOptions struct {
|
||||
WSEventManager events.IWSEventManager
|
||||
Logger *zerolog.Logger
|
||||
AnilistClientWrapper *anilist.ClientWrapper
|
||||
AnilistClientWrapper anilist.ClientWrapperInterface
|
||||
AnilistCollection *anilist.AnimeCollection
|
||||
Database *db.Database
|
||||
RefreshAnilistCollectionFunc func() // This function is called to refresh the AniList collection
|
||||
@@ -83,7 +83,7 @@ func New(opts *NewProgressManagerOptions) *PlaybackManager {
|
||||
}
|
||||
}
|
||||
|
||||
func (pm *PlaybackManager) SetAnilistClientWrapper(anilistClientWrapper *anilist.ClientWrapper) {
|
||||
func (pm *PlaybackManager) SetAnilistClientWrapper(anilistClientWrapper anilist.ClientWrapperInterface) {
|
||||
pm.anilistClientWrapper = anilistClientWrapper
|
||||
}
|
||||
|
||||
|
||||
@@ -6,20 +6,27 @@ import (
|
||||
"github.com/seanime-app/seanime/internal/db"
|
||||
"github.com/seanime-app/seanime/internal/events"
|
||||
"github.com/seanime-app/seanime/internal/playbackmanager"
|
||||
"github.com/seanime-app/seanime/internal/test_utils"
|
||||
"github.com/seanime-app/seanime/internal/util"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func getPlaybackManager() (*playbackmanager.PlaybackManager, *anilist.ClientWrapper, *anilist.AnimeCollection, error) {
|
||||
logger := util.NewLogger()
|
||||
wsEventManager := events.NewMockWSEventManager(logger)
|
||||
databaseInfo := db.GetTestDatabaseInfo()
|
||||
database, err := db.NewDatabase(databaseInfo.DataDir, databaseInfo.Name, logger)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
anilistClientWrapper, data := anilist.TestGetAnilistClientWrapperAndInfo()
|
||||
func getPlaybackManager(t *testing.T) (*playbackmanager.PlaybackManager, anilist.ClientWrapperInterface, *anilist.AnimeCollection, error) {
|
||||
test_utils.InitTestProvider(t, test_utils.Anilist())
|
||||
|
||||
anilistCollection, err := anilistClientWrapper.Client.AnimeCollection(context.Background(), &data.AnilistUsername)
|
||||
logger := util.NewLogger()
|
||||
|
||||
wsEventManager := events.NewMockWSEventManager(logger)
|
||||
|
||||
database, err := db.NewDatabase(test_utils.ConfigData.Path.DataDir, test_utils.ConfigData.Database.Name, logger)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("error while creating database, %v", err)
|
||||
}
|
||||
|
||||
acw := anilist.TestGetAnilistClientWrapper()
|
||||
|
||||
anilistCollection, err := acw.AnimeCollection(context.Background(), &test_utils.ConfigData.Provider.AnilistUsername)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
@@ -27,11 +34,11 @@ func getPlaybackManager() (*playbackmanager.PlaybackManager, *anilist.ClientWrap
|
||||
return playbackmanager.New(&playbackmanager.NewProgressManagerOptions{
|
||||
Logger: logger,
|
||||
WSEventManager: wsEventManager,
|
||||
AnilistClientWrapper: anilistClientWrapper,
|
||||
AnilistClientWrapper: acw,
|
||||
Database: database,
|
||||
AnilistCollection: anilistCollection,
|
||||
RefreshAnilistCollectionFunc: func() {
|
||||
// Do nothing
|
||||
},
|
||||
}), anilistClientWrapper, anilistCollection, nil
|
||||
}), acw, anilistCollection, nil
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ var localFilePaths = []string{
|
||||
var mediaId = 153518
|
||||
|
||||
func TestPlaylists(t *testing.T) {
|
||||
playbackManager, anilistClientWrapper, anilistCollection, err := getPlaybackManager()
|
||||
playbackManager, anilistClientWrapper, anilistCollection, err := getPlaybackManager(t)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ type (
|
||||
scannedCh chan struct{}
|
||||
waitTime time.Duration // Wait time to listen to additional changes before triggering a scan.
|
||||
enabled bool
|
||||
AnilistClientWrapper *anilist.ClientWrapper
|
||||
AnilistClientWrapper anilist.ClientWrapperInterface
|
||||
Logger *zerolog.Logger
|
||||
WSEventManager events.IWSEventManager
|
||||
Database *db.Database // Database instance is required to update the local files.
|
||||
@@ -30,7 +30,7 @@ type (
|
||||
}
|
||||
NewAutoScannerOptions struct {
|
||||
Database *db.Database
|
||||
AnilistClientWrapper *anilist.ClientWrapper
|
||||
AnilistClientWrapper anilist.ClientWrapperInterface
|
||||
Logger *zerolog.Logger
|
||||
WSEventManager events.IWSEventManager
|
||||
Enabled bool
|
||||
|
||||
@@ -3,17 +3,19 @@ package scanner
|
||||
import (
|
||||
"github.com/seanime-app/seanime/internal/anilist"
|
||||
"github.com/seanime-app/seanime/internal/events"
|
||||
"github.com/seanime-app/seanime/internal/test_utils"
|
||||
"github.com/seanime-app/seanime/internal/util"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestAutoScanner(t *testing.T) {
|
||||
test_utils.InitTestProvider(t, test_utils.Anilist())
|
||||
|
||||
doneCh := make(chan struct{})
|
||||
|
||||
logger := util.NewLogger()
|
||||
anilistClientWrapper, _ := anilist.TestGetAnilistClientWrapperAndInfo()
|
||||
anilistClientWrapper := anilist.TestGetMockAnilistClientWrapper()
|
||||
|
||||
as := NewAutoScanner(&NewAutoScannerOptions{
|
||||
Database: nil,
|
||||
|
||||
@@ -24,7 +24,7 @@ type FileHydrator struct {
|
||||
AllMedia []*entities.NormalizedMedia // All media used to hydrate local files
|
||||
BaseMediaCache *anilist.BaseMediaCache
|
||||
AnizipCache *anizip.Cache
|
||||
AnilistClientWrapper *anilist.ClientWrapper
|
||||
AnilistClientWrapper anilist.ClientWrapperInterface
|
||||
AnilistRateLimiter *limiter.Limiter
|
||||
Logger *zerolog.Logger
|
||||
ScanLogger *ScanLogger // optional
|
||||
|
||||
@@ -29,7 +29,7 @@ type MediaFetcher struct {
|
||||
type MediaFetcherOptions struct {
|
||||
Enhanced bool
|
||||
Username string
|
||||
AnilistClientWrapper *anilist.ClientWrapper
|
||||
AnilistClientWrapper anilist.ClientWrapperInterface
|
||||
LocalFiles []*entities.LocalFile
|
||||
BaseMediaCache *anilist.BaseMediaCache
|
||||
AnizipCache *anizip.Cache
|
||||
@@ -75,7 +75,7 @@ func NewMediaFetcher(opts *MediaFetcherOptions) (ret *MediaFetcher, retErr error
|
||||
// +---------------------+
|
||||
|
||||
// Fetch latest user's AniList collection
|
||||
animeCollection, err := opts.AnilistClientWrapper.Client.AnimeCollection(context.Background(), &opts.Username)
|
||||
animeCollection, err := opts.AnilistClientWrapper.AnimeCollection(context.Background(), &opts.Username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -174,7 +174,7 @@ func NewMediaFetcher(opts *MediaFetcherOptions) (ret *MediaFetcher, retErr error
|
||||
// It does not return an error if one of the steps fails.
|
||||
// It returns the scanned media and a boolean indicating whether the process was successful.
|
||||
func FetchMediaFromLocalFiles(
|
||||
anilistClientWrapper *anilist.ClientWrapper,
|
||||
anilistClientWrapper anilist.ClientWrapperInterface,
|
||||
localFiles []*entities.LocalFile,
|
||||
baseMediaCache *anilist.BaseMediaCache,
|
||||
anizipCache *anizip.Cache,
|
||||
@@ -261,7 +261,7 @@ func FetchMediaFromLocalFiles(
|
||||
anilistMedia := make([]*anilist.BaseMedia, 0)
|
||||
lop.ForEach(anilistIds, func(id int, index int) {
|
||||
anilistRateLimiter.Wait()
|
||||
media, err := anilist.GetBaseMediaById(anilistClientWrapper.Client, id)
|
||||
media, err := anilist.GetBaseMediaById(anilistClientWrapper, id)
|
||||
if err == nil {
|
||||
anilistMedia = append(anilistMedia, media)
|
||||
if scanLogger != nil {
|
||||
|
||||
@@ -107,7 +107,7 @@ func TestNewMediaFetcher(t *testing.T) {
|
||||
|
||||
func TestNewEnhancedMediaFetcher(t *testing.T) {
|
||||
|
||||
anilistClientWrapper, _ := anilist.TestGetAnilistClientWrapperAndInfo()
|
||||
acw := anilist.TestGetAnilistClientWrapper()
|
||||
anizipCache := anizip.NewCache()
|
||||
baseMediaCache := anilist.NewBaseMediaCache()
|
||||
anilistRateLimiter := limiter.NewAnilistLimiter()
|
||||
@@ -157,7 +157,7 @@ func TestNewEnhancedMediaFetcher(t *testing.T) {
|
||||
mf, err := NewMediaFetcher(&MediaFetcherOptions{
|
||||
Enhanced: tt.enhanced,
|
||||
Username: "-",
|
||||
AnilistClientWrapper: anilistClientWrapper,
|
||||
AnilistClientWrapper: acw,
|
||||
LocalFiles: lfs,
|
||||
BaseMediaCache: baseMediaCache,
|
||||
AnizipCache: anizipCache,
|
||||
@@ -186,7 +186,7 @@ func TestNewEnhancedMediaFetcher(t *testing.T) {
|
||||
|
||||
func TestFetchMediaFromLocalFiles(t *testing.T) {
|
||||
|
||||
anilistClientWrapper := anilist.TestGetAnilistClientWrapper()
|
||||
acw := anilist.TestGetAnilistClientWrapper()
|
||||
anizipCache := anizip.NewCache()
|
||||
baseMediaCache := anilist.NewBaseMediaCache()
|
||||
anilistRateLimiter := limiter.NewAnilistLimiter()
|
||||
@@ -234,7 +234,7 @@ func TestFetchMediaFromLocalFiles(t *testing.T) {
|
||||
// +--------------------------+
|
||||
|
||||
media, ok := FetchMediaFromLocalFiles(
|
||||
anilistClientWrapper,
|
||||
acw,
|
||||
lfs,
|
||||
baseMediaCache,
|
||||
anizipCache,
|
||||
|
||||
@@ -112,7 +112,7 @@ func TestMediaTreeAnalysis2(t *testing.T) {
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
media, err := anilistClientWrapper.Client.BaseMediaByID(context.Background(), &tt.mediaId)
|
||||
media, err := anilistClientWrapper.BaseMediaByID(context.Background(), &tt.mediaId)
|
||||
if err != nil {
|
||||
t.Fatal("expected media, got error:", err.Error())
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ type Scanner struct {
|
||||
DirPath string
|
||||
Username string
|
||||
Enhanced bool
|
||||
AnilistClientWrapper *anilist.ClientWrapper
|
||||
AnilistClientWrapper anilist.ClientWrapperInterface
|
||||
Logger *zerolog.Logger
|
||||
WSEventManager events.IWSEventManager
|
||||
ExistingLocalFiles []*entities.LocalFile
|
||||
@@ -230,7 +230,7 @@ func (scn *Scanner) Scan() (lfs []*entities.LocalFile, err error) {
|
||||
if len(mf.UnknownMediaIds) < 5 {
|
||||
scn.WSEventManager.SendEvent(events.EventScanStatus, "Adding missing media to AniList...")
|
||||
|
||||
if err = scn.AnilistClientWrapper.Client.AddMediaToPlanning(mf.UnknownMediaIds, anilistRateLimiter, scn.Logger); err != nil {
|
||||
if err = scn.AnilistClientWrapper.AddMediaToPlanning(mf.UnknownMediaIds, anilistRateLimiter, scn.Logger); err != nil {
|
||||
scn.Logger.Warn().Msg("scanner: An error occurred while adding media to planning list: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"github.com/seanime-app/seanime/internal/anilist"
|
||||
"github.com/seanime-app/seanime/internal/entities"
|
||||
"github.com/seanime-app/seanime/internal/events"
|
||||
"github.com/seanime-app/seanime/internal/test_utils"
|
||||
"github.com/seanime-app/seanime/internal/util"
|
||||
"io"
|
||||
"os"
|
||||
@@ -15,8 +16,9 @@ import (
|
||||
//----------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
func TestScanner_Scan(t *testing.T) {
|
||||
test_utils.InitTestProvider(t, test_utils.Anilist())
|
||||
|
||||
anilistClientWrapper, data := anilist.TestGetAnilistClientWrapperAndInfo()
|
||||
acw := anilist.TestGetAnilistClientWrapper()
|
||||
wsEventManager := events.NewMockWSEventManager(util.NewLogger())
|
||||
dir := "E:/Anime"
|
||||
|
||||
@@ -51,9 +53,9 @@ func TestScanner_Scan(t *testing.T) {
|
||||
|
||||
scanner := &Scanner{
|
||||
DirPath: dir,
|
||||
Username: data.Username,
|
||||
Username: test_utils.ConfigData.Provider.AnilistUsername,
|
||||
Enhanced: false,
|
||||
AnilistClientWrapper: anilistClientWrapper,
|
||||
AnilistClientWrapper: acw,
|
||||
Logger: util.NewLogger(),
|
||||
WSEventManager: wsEventManager,
|
||||
ExistingLocalFiles: existingLfs,
|
||||
|
||||
7
internal/test_utils/cache.go
Normal file
7
internal/test_utils/cache.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package test_utils
|
||||
|
||||
import "fmt"
|
||||
|
||||
func GetTestDataPath(name string) string {
|
||||
return fmt.Sprintf("%s/%s.json", TwoLevelDeepTestDataPath, name)
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
)
|
||||
|
||||
var ConfigData = &Config{}
|
||||
var TwoLevelDeepTestDataPath = "../../test/testdata"
|
||||
|
||||
type (
|
||||
Config struct {
|
||||
@@ -65,7 +66,7 @@ func MyAnimeListMutation() FlagFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// InitTestProvider initializes the ConfigData and skips the test if the given flags are not set
|
||||
// InitTestProvider populates the ConfigData and skips the test if the given flags are not set
|
||||
func InitTestProvider(t *testing.T, args ...FlagFunc) {
|
||||
err := os.Setenv("TEST_CONFIG_PATH", "../../test")
|
||||
ConfigData = getConfig()
|
||||
@@ -80,10 +81,6 @@ func InitTestProvider(t *testing.T, args ...FlagFunc) {
|
||||
}
|
||||
}
|
||||
|
||||
func SetConfigPath(relPath string) {
|
||||
os.Setenv("TEST_CONFIG_PATH", relPath)
|
||||
}
|
||||
|
||||
func getConfig() *Config {
|
||||
configPath, exists := os.LookupEnv("TEST_CONFIG_PATH")
|
||||
if !exists {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package test_utils
|
||||
|
||||
import (
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetConfig(t *testing.T) {
|
||||
assert.Empty(t, ConfigData)
|
||||
|
||||
assert.NotNil(t, ConfigData)
|
||||
InitTestProvider(t)
|
||||
|
||||
spew.Dump(ConfigData)
|
||||
assert.NotEmpty(t, ConfigData)
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ func TestSmartSelect(t *testing.T) {
|
||||
t.Log(tt.name, hash)
|
||||
|
||||
// get media
|
||||
media, err := anilist.GetBaseMediaById(anilistClientWrapper.Client, tt.mediaId)
|
||||
media, err := anilist.GetBaseMediaById(anilistClientWrapper, tt.mediaId)
|
||||
if err != nil {
|
||||
t.Fatalf("error getting media: %s", err.Error())
|
||||
}
|
||||
|
||||
16
test/config.example.toml
Normal file
16
test/config.example.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[flags]
|
||||
enable_anilist_tests = true
|
||||
enable_anilist_mutation_tests = false
|
||||
enable_mal_tests = true
|
||||
enable_mal_mutation_tests = false
|
||||
|
||||
[provider]
|
||||
anilist_jwt = ''
|
||||
anilist_username = ''
|
||||
mal_jwt = ''
|
||||
|
||||
[path]
|
||||
dataDir = ''
|
||||
|
||||
[database]
|
||||
name = 'seanime-test'
|
||||
0
test/testdata/.gitkeep
vendored
Normal file
0
test/testdata/.gitkeep
vendored
Normal file
Reference in New Issue
Block a user