refactored tests

This commit is contained in:
5rahim
2026-03-31 16:50:49 +02:00
parent da92c5976b
commit 47f3d39a28
53 changed files with 2334 additions and 25377 deletions

View File

@@ -188,24 +188,45 @@ go mod tidy
#### Writing Tests
Tests use the `test_utils` package which provides:
- `InitTestProvider` method to initialize the test configuration
- Flags to enable/disable specific test categories
Tests use the `internal/testutil` package which provides:
- `InitTestProvider` to load test configuration and apply feature-flag skips
- `NewTestEnv` to create an isolated temp root, app data dir, cache dir, and database for tests
- `FixtureRelPath` and fixture helpers
- `RequireSampleVideoPath` for media-player tests that need a real sample file
Example:
```go
func TestSomething(t *testing.T) {
test_utils.InitTestProvider(t, test_utils.Anilist())
// Test code here
env := testutil.NewTestEnv(t, testutil.Anilist())
database := env.MustNewDatabase(util.NewLogger())
_ = database
}
```
AniList mock fixtures are read-only during normal test runs. Set `SEANIME_TEST_RECORD_ANILIST_FIXTURES=true` when you intentionally want missing or refreshed fixtures written back to the repository.
To avoid remembering the environment variable and basic auth checks, use the refresh wrapper:
```bash
go run ./scripts/record_anilist_fixtures
```
Notes:
- It validates that `test/config.toml` exists, `flags.enable_anilist_tests=true`, and `provider.anilist_jwt` is set.
- It defaults to refreshing `./internal/api/anilist` and sets `SEANIME_TEST_RECORD_ANILIST_FIXTURES=true` for the test process.
- Pass packages to widen the refresh scope, for example `go run ./scripts/record_anilist_fixtures ./internal/api/anilist ./internal/library/scanner`.
- Pass `-run` to target specific live refresh tests, for example `go run ./scripts/record_anilist_fixtures -run 'TestGetAnimeByIdLive|TestBaseAnime_FetchMediaTree_BaseAnimeLive'`.
#### Testing with Third-Party Apps
Some tests interact with applications like Transmission and qBittorrent:
- Ensure these applications are installed and running
- Configure `test/config.toml` with appropriate connection details
Media-player tests that open a file also require `path.sampleVideoPath` in `test/config.toml`, or `TEST_SAMPLE_VIDEO_PATH` in the environment.
## Notes and Warnings
- hls.js versions 1.6.0 and above may cause appendBuffer fatal errors

File diff suppressed because it is too large Load Diff

View File

@@ -3,71 +3,93 @@ package anilist
import (
"context"
"os"
"path/filepath"
"seanime/internal/testutil"
"strconv"
"strings"
"testing"
"github.com/goccy/go-json"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// USE CASE: Generate a boilerplate Anilist AnimeCollection for testing purposes and save it to 'test/data/BoilerplateAnimeCollection'.
// The generated AnimeCollection will have all entries in the 'Planning' status.
// The generated AnimeCollection will be used to test various Anilist API methods.
// You can use TestModifyAnimeCollectionEntry to modify the generated AnimeCollection before using it in a test.
// - DO NOT RUN IF YOU DON'T PLAN TO GENERATE A NEW 'test/data/BoilerplateAnimeCollection'
func TestGenerateBoilerplateAnimeCollection(t *testing.T) {
t.Skip("This test is not meant to be run")
cfg := testutil.InitTestProvider(t, testutil.Anilist())
const recordCompleteAnimeIDsEnvName = "SEANIME_TEST_RECORD_COMPLETE_ANIME_IDS"
anilistClient := TestGetMockAnilistClient()
func TestMaybeWriteJSONFixtureCreatesDirectories(t *testing.T) {
t.Setenv(testutil.RecordAnilistFixturesEnvName, "true")
ac, err := anilistClient.AnimeCollection(context.Background(), &cfg.Provider.AnilistUsername)
target := filepath.Join(t.TempDir(), "fixtures", "nested", "fixture.json")
err := maybeWriteJSONFixture(target, map[string]string{"status": "ok"}, nil)
assert.NoError(t, err)
if assert.NoError(t, err) {
_, err = os.Stat(target)
assert.NoError(t, err)
}
lists := ac.GetMediaListCollection().GetLists()
func TestCustomQueryFixturePathIsStable(t *testing.T) {
body := []byte(`{"query":"query Example { Page { pageInfo { total } } }","variables":{"page":1}}`)
entriesToAddToPlanning := make([]*AnimeListEntry, 0)
path1 := customQueryFixturePath(body)
path2 := customQueryFixturePath(body)
if assert.NoError(t, err) {
assert.Equal(t, path1, path2)
assert.Contains(t, path1, filepath.Join("test", "testdata", "anilist-custom-query"))
}
for _, list := range lists {
if list.Status != nil {
if list.GetStatus().String() != string(MediaListStatusPlanning) {
entries := list.GetEntries()
for _, entry := range entries {
entry.Progress = new(0)
entry.Score = new(0.0)
entry.Status = new(MediaListStatusPlanning)
entriesToAddToPlanning = append(entriesToAddToPlanning, entry)
}
list.Entries = make([]*AnimeListEntry, 0)
}
}
}
func TestFixtureMangaCollectionUsesCommittedFixture(t *testing.T) {
client := NewFixtureAnilistClient()
newLists := make([]*AnimeCollection_MediaListCollection_Lists, 0)
for _, list := range lists {
if list.Status == nil {
continue
}
if *list.GetStatus() == MediaListStatusPlanning {
list.Entries = append(list.Entries, entriesToAddToPlanning...)
newLists = append(newLists, list)
} else {
newLists = append(newLists, list)
}
}
collection, err := client.MangaCollection(context.Background(), nil)
require.NoError(t, err)
require.NotNil(t, collection)
ac.MediaListCollection.Lists = newLists
data, err := json.Marshal(ac)
if assert.NoError(t, err) {
err = os.WriteFile(testutil.DataPath("BoilerplateAnimeCollection"), data, 0644)
assert.NoError(t, err)
}
}
entry, found := collection.GetListEntryFromMangaId(101517)
require.True(t, found)
require.Equal(t, MediaListStatusCurrent, *entry.GetStatus())
require.Equal(t, 260, *entry.GetProgress())
}
func TestRecordCompleteAnimeByIDFixtures(t *testing.T) {
if !testutil.ShouldRecordAnilistFixtures() {
t.Skip("AniList fixture recording disabled")
}
rawIDs := strings.TrimSpace(os.Getenv(recordCompleteAnimeIDsEnvName))
if rawIDs == "" {
t.Skip("no CompleteAnimeByID fixture ids requested")
}
cfg := testutil.LoadConfig(t)
if cfg.Provider.AnilistJwt == "" {
t.Skip("AniList fixture recording requires provider.anilist_jwt")
}
client := NewFixtureAnilistClientWithToken(cfg.Provider.AnilistJwt)
for _, mediaID := range parseFixtureMediaIDs(t, rawIDs) {
_, err := client.CompleteAnimeByID(context.Background(), &mediaID)
require.NoErrorf(t, err, "failed to record CompleteAnimeByID fixture for media %d", mediaID)
}
}
func parseFixtureMediaIDs(t *testing.T, raw string) []int {
t.Helper()
parts := strings.FieldsFunc(raw, func(r rune) bool {
switch r {
case ',', ' ', '\n', '\t':
return true
default:
return false
}
})
require.NotEmpty(t, parts, "expected at least one media id")
ids := make([]int, 0, len(parts))
for _, part := range parts {
mediaID, err := strconv.Atoi(part)
require.NoErrorf(t, err, "invalid media id %q", part)
ids = append(ids, mediaID)
}
return ids
}

View File

@@ -2,7 +2,6 @@ package anilist
import (
"context"
"seanime/internal/testutil"
"seanime/internal/util"
"testing"
@@ -10,145 +9,16 @@ import (
"github.com/stretchr/testify/assert"
)
//func TestHiddenFromStatus(t *testing.T) {
// test_utils.InitTestProvider(t, test_utils.Anilist())
//
// cfg := test_utils.InitTestProvider(t, test_utils.Anilist())
// token := cfg.Provider.AnilistJwt
// logger := util.NewLogger()
// //anilistClient := NewAnilistClient(cfg.Provider.AnilistJwt)
//
// variables := map[string]interface{}{}
//
// variables["userName"] = cfg.Provider.AnilistUsername
// variables["type"] = "ANIME"
//
// requestBody, err := json.Marshal(map[string]interface{}{
// "query": testQuery,
// "variables": variables,
// })
// require.NoError(t, err)
//
// data, err := customQuery(requestBody, logger, token)
// require.NoError(t, err)
//
// var mediaLists []*MediaList
//
// type retData struct {
// Page Page
// PageInfo PageInfo
// }
//
// var ret retData
// m, err := json.Marshal(data)
// require.NoError(t, err)
// if err := json.Unmarshal(m, &ret); err != nil {
// t.Fatalf("Failed to unmarshal data: %v", err)
// }
//
// mediaLists = append(mediaLists, ret.Page.MediaList...)
//
// util.Spew(ret.Page.PageInfo)
//
// var currentPage = 1
// var hasNextPage = false
// if ret.Page.PageInfo != nil && ret.Page.PageInfo.HasNextPage != nil {
// hasNextPage = *ret.Page.PageInfo.HasNextPage
// }
// for hasNextPage {
// currentPage++
// variables["page"] = currentPage
// requestBody, err = json.Marshal(map[string]interface{}{
// "query": testQuery,
// "variables": variables,
// })
// require.NoError(t, err)
// data, err = customQuery(requestBody, logger, token)
// require.NoError(t, err)
// m, err = json.Marshal(data)
// require.NoError(t, err)
// if err := json.Unmarshal(m, &ret); err != nil {
// t.Fatalf("Failed to unmarshal data: %v", err)
// }
// util.Spew(ret.Page.PageInfo)
// if ret.Page.PageInfo != nil && ret.Page.PageInfo.HasNextPage != nil {
// hasNextPage = *ret.Page.PageInfo.HasNextPage
// }
// mediaLists = append(mediaLists, ret.Page.MediaList...)
// }
//
// //res, err := anilistClient.AnimeCollection(context.Background(), &cfg.Provider.AnilistUsername)
// //assert.NoError(t, err)
//
// for _, mediaList := range mediaLists {
// util.Spew(mediaList.Media.ID)
// if mediaList.Media.ID == 151514 {
// util.Spew(mediaList)
// }
// }
//
//}
//
//const testQuery = `query ($page: Int, $userName: String, $type: MediaType) {
// Page (page: $page, perPage: 100) {
// pageInfo {
// hasNextPage
// total
// perPage
// currentPage
// lastPage
// }
// mediaList (type: $type, userName: $userName) {
// status
// startedAt {
// year
// month
// day
// }
// completedAt {
// year
// month
// day
// }
// repeat
// score(format: POINT_100)
// progress
// progressVolumes
// notes
// media {
// siteUrl
// id
// idMal
// episodes
// chapters
// volumes
// status
// averageScore
// coverImage{
// large
// extraLarge
// }
// bannerImage
// title {
// userPreferred
// }
// }
// }
// }
// }`
func TestGetAnimeById(t *testing.T) {
testutil.InitTestProvider(t, testutil.Anilist())
anilistClient := TestGetMockAnilistClient()
anilistClient := NewTestAnilistClient()
tests := []struct {
name string
mediaId int
}{
{
name: "Cowboy Bebop",
mediaId: 1,
name: "Re:Zero",
mediaId: 21355,
},
}
@@ -161,9 +31,16 @@ func TestGetAnimeById(t *testing.T) {
}
}
func TestListAnime(t *testing.T) {
testutil.InitTestProvider(t, testutil.Anilist())
func TestGetAnimeByIdLive(t *testing.T) {
anilistClient := newLiveAnilistClient(t)
mediaID := 1
res, err := anilistClient.BaseAnimeByID(context.Background(), &mediaID)
assert.NoError(t, err)
assert.NotNil(t, res)
}
func TestListAnime(t *testing.T) {
tests := []struct {
name string
Page *int
@@ -196,7 +73,7 @@ func TestListAnime(t *testing.T) {
},
}
anilistClient := TestGetMockAnilistClient()
anilistClient := NewTestAnilistClient()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

View File

@@ -12,7 +12,7 @@ import (
)
func TestCompoundQuery(t *testing.T) {
testutil.InitTestProvider(t)
testutil.InitTestProvider(t, testutil.Anilist())
var ids = []int{171457, 21}

View File

@@ -2,7 +2,6 @@ package anilist
import (
"context"
"seanime/internal/testutil"
"seanime/internal/util/limiter"
"testing"
@@ -11,9 +10,7 @@ import (
)
func TestBaseAnime_FetchMediaTree_BaseAnime(t *testing.T) {
testutil.InitTestProvider(t, testutil.Anilist())
anilistClient := TestGetMockAnilistClient()
anilistClient := NewTestAnilistClient()
lim := limiter.NewAnilistLimiter()
completeAnimeCache := NewCompleteAnimeCache()
@@ -33,16 +30,6 @@ func TestBaseAnime_FetchMediaTree_BaseAnime(t *testing.T) {
163263, // BSD5
},
},
{
name: "Re:Zero",
mediaId: 21355,
edgeIds: []int{
21355, // Re:Zero 1
108632, // Re:Zero 2
119661, // Re:Zero 2 Part 2
163134, // Re:Zero 3
},
},
}
for _, tt := range tests {
@@ -81,3 +68,36 @@ func TestBaseAnime_FetchMediaTree_BaseAnime(t *testing.T) {
}
}
func TestBaseAnime_FetchMediaTree_BaseAnimeLive(t *testing.T) {
anilistClient := newLiveAnilistClient(t)
lim := limiter.NewAnilistLimiter()
completeAnimeCache := NewCompleteAnimeCache()
mediaID := 21355
edgeIDs := []int{21355, 108632, 119661, 163134}
mediaF, err := anilistClient.CompleteAnimeByID(context.Background(), &mediaID)
if !assert.NoError(t, err) {
return
}
media := mediaF.GetMedia()
tree := NewCompleteAnimeRelationTree()
err = media.FetchMediaTree(
FetchMediaTreeAll,
anilistClient,
lim,
tree,
completeAnimeCache,
)
if !assert.NoError(t, err) {
return
}
for _, treeID := range edgeIDs {
a, found := tree.Get(treeID)
assert.Truef(t, found, "expected tree to contain %d", treeID)
spew.Dump(a.GetTitleSafe())
}
}

View File

@@ -0,0 +1,17 @@
package anilist
import (
"seanime/internal/testutil"
"testing"
)
func newLiveAnilistClient(t testing.TB) AnilistClient {
t.Helper()
cfg := testutil.InitTestProvider(t, testutil.Anilist())
if cfg.Provider.AnilistJwt == "" {
t.Skip("anilist live tests require anilist_jwt")
}
return NewAnilistClient(cfg.Provider.AnilistJwt, "")
}

View File

@@ -0,0 +1,286 @@
package anilist
import (
"context"
"fmt"
)
type AnimeCollectionEntryPatch struct {
Status *MediaListStatus
Progress *int
Score *float64
Repeat *int
AiredEpisodes *int
NextAiringEpisode *BaseAnime_NextAiringEpisode
}
func PatchAnimeCollectionEntry(collection *AnimeCollection, mediaID int, patch AnimeCollectionEntryPatch) *AnimeCollection {
if collection == nil {
panic("anilist: anime collection is nil")
}
entry, currentList := findAnimeCollectionEntry(collection, mediaID)
if entry == nil {
panic(fmt.Sprintf("anilist: anime %d not found in collection; use EnsureAnimeCollectionEntry for missing media", mediaID))
}
if patch.Status != nil {
currentList = moveAnimeEntryToStatus(collection, currentList, entry, *patch.Status)
_ = currentList
}
applyAnimeCollectionEntryPatch(entry, patch)
return collection
}
func EnsureAnimeCollectionEntry(collection *AnimeCollection, mediaID int, patch AnimeCollectionEntryPatch, client AnilistClient) *AnimeCollection {
if collection == nil {
panic("anilist: anime collection is nil")
}
if _, currentList := findAnimeCollectionEntry(collection, mediaID); currentList != nil {
return collection
}
if client == nil {
panic(fmt.Sprintf("anilist: cannot add anime %d without a client", mediaID))
}
if patch.Status == nil {
panic(fmt.Sprintf("anilist: cannot add anime %d without a status", mediaID))
}
baseAnime, err := client.BaseAnimeByID(context.Background(), &mediaID)
if err != nil {
panic(err)
}
entry := &AnimeCollection_MediaListCollection_Lists_Entries{
Media: baseAnime.GetMedia(),
}
list := ensureAnimeStatusList(collection, *patch.Status)
list.Entries = append(list.Entries, entry)
return PatchAnimeCollectionEntry(collection, mediaID, patch)
}
func PatchAnimeCollectionWithRelationsEntry(collection *AnimeCollectionWithRelations, mediaID int, patch AnimeCollectionEntryPatch) *AnimeCollectionWithRelations {
if collection == nil {
panic("anilist: anime collection with relations is nil")
}
entry, currentList := findAnimeCollectionWithRelationsEntry(collection, mediaID)
if entry == nil {
panic(fmt.Sprintf("anilist: anime %d not found in relation collection; use EnsureAnimeCollectionWithRelationsEntry for missing media", mediaID))
}
if patch.Status != nil {
currentList = moveAnimeRelationsEntryToStatus(collection, currentList, entry, *patch.Status)
_ = currentList
}
applyAnimeCollectionWithRelationsEntryPatch(entry, patch)
return collection
}
func EnsureAnimeCollectionWithRelationsEntry(collection *AnimeCollectionWithRelations, mediaID int, patch AnimeCollectionEntryPatch, client AnilistClient) *AnimeCollectionWithRelations {
if collection == nil {
panic("anilist: anime collection with relations is nil")
}
if _, currentList := findAnimeCollectionWithRelationsEntry(collection, mediaID); currentList != nil {
return collection
}
if client == nil {
panic(fmt.Sprintf("anilist: cannot add anime %d without a client", mediaID))
}
if patch.Status == nil {
panic(fmt.Sprintf("anilist: cannot add anime %d without a status", mediaID))
}
completeAnime, err := client.CompleteAnimeByID(context.Background(), &mediaID)
if err != nil {
panic(err)
}
entry := &AnimeCollectionWithRelations_MediaListCollection_Lists_Entries{
Media: completeAnime.GetMedia(),
}
list := ensureAnimeRelationsStatusList(collection, *patch.Status)
list.Entries = append(list.Entries, entry)
return PatchAnimeCollectionWithRelationsEntry(collection, mediaID, patch)
}
func applyAnimeCollectionEntryPatch(entry *AnimeCollection_MediaListCollection_Lists_Entries, patch AnimeCollectionEntryPatch) {
if patch.Status != nil {
entry.Status = patch.Status
}
if patch.Progress != nil {
entry.Progress = patch.Progress
}
if patch.Score != nil {
entry.Score = patch.Score
}
if patch.Repeat != nil {
entry.Repeat = patch.Repeat
}
if patch.AiredEpisodes != nil {
entry.Media.Episodes = patch.AiredEpisodes
}
if patch.NextAiringEpisode != nil {
entry.Media.NextAiringEpisode = patch.NextAiringEpisode
}
}
func applyAnimeCollectionWithRelationsEntryPatch(entry *AnimeCollectionWithRelations_MediaListCollection_Lists_Entries, patch AnimeCollectionEntryPatch) {
if patch.Status != nil {
entry.Status = patch.Status
}
if patch.Progress != nil {
entry.Progress = patch.Progress
}
if patch.Score != nil {
entry.Score = patch.Score
}
if patch.Repeat != nil {
entry.Repeat = patch.Repeat
}
if patch.AiredEpisodes != nil {
entry.Media.Episodes = patch.AiredEpisodes
}
}
func findAnimeCollectionEntry(collection *AnimeCollection, mediaID int) (*AnimeCollection_MediaListCollection_Lists_Entries, *AnimeCollection_MediaListCollection_Lists) {
if collection == nil || collection.MediaListCollection == nil {
return nil, nil
}
for _, list := range collection.MediaListCollection.Lists {
if list == nil || list.Entries == nil {
continue
}
for _, entry := range list.Entries {
if entry != nil && entry.GetMedia().GetID() == mediaID {
return entry, list
}
}
}
return nil, nil
}
func findAnimeCollectionWithRelationsEntry(collection *AnimeCollectionWithRelations, mediaID int) (*AnimeCollectionWithRelations_MediaListCollection_Lists_Entries, *AnimeCollectionWithRelations_MediaListCollection_Lists) {
if collection == nil || collection.MediaListCollection == nil {
return nil, nil
}
for _, list := range collection.MediaListCollection.Lists {
if list == nil || list.Entries == nil {
continue
}
for _, entry := range list.Entries {
if entry != nil && entry.GetMedia().GetID() == mediaID {
return entry, list
}
}
}
return nil, nil
}
func moveAnimeEntryToStatus(collection *AnimeCollection, currentList *AnimeCollection_MediaListCollection_Lists, entry *AnimeCollection_MediaListCollection_Lists_Entries, status MediaListStatus) *AnimeCollection_MediaListCollection_Lists {
if currentList != nil && currentList.Status != nil && *currentList.Status == status {
return currentList
}
if currentList != nil {
removeAnimeEntry(currentList, entry.GetMedia().GetID())
}
target := ensureAnimeStatusList(collection, status)
target.Entries = append(target.Entries, entry)
return target
}
func moveAnimeRelationsEntryToStatus(collection *AnimeCollectionWithRelations, currentList *AnimeCollectionWithRelations_MediaListCollection_Lists, entry *AnimeCollectionWithRelations_MediaListCollection_Lists_Entries, status MediaListStatus) *AnimeCollectionWithRelations_MediaListCollection_Lists {
if currentList != nil && currentList.Status != nil && *currentList.Status == status {
return currentList
}
if currentList != nil {
removeAnimeRelationsEntry(currentList, entry.GetMedia().GetID())
}
target := ensureAnimeRelationsStatusList(collection, status)
target.Entries = append(target.Entries, entry)
return target
}
func ensureAnimeStatusList(collection *AnimeCollection, status MediaListStatus) *AnimeCollection_MediaListCollection_Lists {
if collection.MediaListCollection == nil {
collection.MediaListCollection = &AnimeCollection_MediaListCollection{}
}
for _, list := range collection.MediaListCollection.Lists {
if list != nil && list.Status != nil && *list.Status == status {
if list.Entries == nil {
list.Entries = []*AnimeCollection_MediaListCollection_Lists_Entries{}
}
return list
}
}
name := string(status)
isCustomList := false
list := &AnimeCollection_MediaListCollection_Lists{
Status: testPointer(status),
Name: &name,
IsCustomList: &isCustomList,
Entries: []*AnimeCollection_MediaListCollection_Lists_Entries{},
}
collection.MediaListCollection.Lists = append(collection.MediaListCollection.Lists, list)
return list
}
func ensureAnimeRelationsStatusList(collection *AnimeCollectionWithRelations, status MediaListStatus) *AnimeCollectionWithRelations_MediaListCollection_Lists {
if collection.MediaListCollection == nil {
collection.MediaListCollection = &AnimeCollectionWithRelations_MediaListCollection{}
}
for _, list := range collection.MediaListCollection.Lists {
if list != nil && list.Status != nil && *list.Status == status {
if list.Entries == nil {
list.Entries = []*AnimeCollectionWithRelations_MediaListCollection_Lists_Entries{}
}
return list
}
}
name := string(status)
isCustomList := false
list := &AnimeCollectionWithRelations_MediaListCollection_Lists{
Status: testPointer(status),
Name: &name,
IsCustomList: &isCustomList,
Entries: []*AnimeCollectionWithRelations_MediaListCollection_Lists_Entries{},
}
collection.MediaListCollection.Lists = append(collection.MediaListCollection.Lists, list)
return list
}
func removeAnimeEntry(list *AnimeCollection_MediaListCollection_Lists, mediaID int) {
for idx, entry := range list.GetEntries() {
if entry != nil && entry.GetMedia().GetID() == mediaID {
list.Entries = append(list.Entries[:idx], list.Entries[idx+1:]...)
return
}
}
}
func removeAnimeRelationsEntry(list *AnimeCollectionWithRelations_MediaListCollection_Lists, mediaID int) {
for idx, entry := range list.GetEntries() {
if entry != nil && entry.GetMedia().GetID() == mediaID {
list.Entries = append(list.Entries[:idx], list.Entries[idx+1:]...)
return
}
}
}
func testPointer[T any](value T) *T {
return &value
}

View File

@@ -1,22 +0,0 @@
package metadata_provider
import (
"seanime/internal/database/db"
"seanime/internal/extension"
"seanime/internal/util"
"seanime/internal/util/filecache"
"testing"
"github.com/stretchr/testify/require"
)
func GetFakeProvider(t *testing.T, db *db.Database) Provider {
filecacher, err := filecache.NewCacher(t.TempDir())
require.NoError(t, err)
return NewProvider(&NewProviderImplOptions{
Logger: util.NewLogger(),
FileCacher: filecacher,
Database: db,
ExtensionBankRef: util.NewRef(extension.NewUnifiedBank()),
})
}

View File

@@ -10,7 +10,7 @@ import (
func TestProvider(t *testing.T) {
metadataProvider := GetFakeProvider(t, nil)
metadataProvider := NewTestProvider(t, nil)
tests := []struct {
platform metadata.Platform

View File

@@ -0,0 +1,24 @@
package metadata_provider
import (
"seanime/internal/database/db"
"seanime/internal/extension"
"seanime/internal/testutil"
"seanime/internal/util"
"testing"
)
func NewTestProvider(t *testing.T, db *db.Database) Provider {
t.Helper()
return NewTestProviderWithEnv(testutil.NewTestEnv(t), db)
}
func NewTestProviderWithEnv(env *testutil.TestEnv, db *db.Database) Provider {
return NewProvider(&NewProviderImplOptions{
Logger: env.Logger(),
FileCacher: env.NewCacher("metadata-provider"),
Database: db,
ExtensionBankRef: util.NewRef(extension.NewUnifiedBank()),
})
}

View File

@@ -1,10 +1,7 @@
package continuity
import (
"path/filepath"
"seanime/internal/database/db"
"seanime/internal/testutil"
"seanime/internal/util"
"seanime/internal/util/filecache"
"testing"
@@ -12,18 +9,10 @@ import (
)
func TestHistoryItems(t *testing.T) {
cfg := testutil.InitTestProvider(t)
logger := util.NewLogger()
tempDir := t.TempDir()
t.Log(tempDir)
database, err := db.NewDatabase(cfg.Path.DataDir, cfg.Database.Name, logger)
require.NoError(t, err)
cacher, err := filecache.NewCacher(filepath.Join(tempDir, "cache"))
require.NoError(t, err)
env := testutil.NewTestEnv(t)
logger := env.Logger()
database := env.NewDatabase("")
cacher := env.NewCacher("continuity")
manager := NewManager(&NewManagerOptions{
FileCacher: cacher,
@@ -32,6 +21,8 @@ func TestHistoryItems(t *testing.T) {
})
require.NotNil(t, manager)
var err error
var mediaIds = make([]int, MaxWatchHistoryItems+1)
for i := 0; i < MaxWatchHistoryItems+1; i++ {
mediaIds[i] = i + 1

View File

@@ -1,15 +1,16 @@
package continuity
import (
"github.com/stretchr/testify/require"
"path/filepath"
"seanime/internal/database/db"
"seanime/internal/util"
"seanime/internal/util/filecache"
"testing"
"github.com/stretchr/testify/require"
)
func GetMockManager(t *testing.T, db *db.Database) *Manager {
func NewTestManager(t *testing.T, db *db.Database) *Manager {
logger := util.NewLogger()
cacher, err := filecache.NewCacher(filepath.Join(t.TempDir(), "cache"))
require.NoError(t, err)

View File

@@ -1,50 +0,0 @@
package debrid_client
import (
"seanime/internal/api/anilist"
"seanime/internal/api/metadata_provider"
"seanime/internal/continuity"
"seanime/internal/database/db"
"seanime/internal/events"
"seanime/internal/extension"
"seanime/internal/library/playbackmanager"
"seanime/internal/platforms/anilist_platform"
"seanime/internal/util"
"testing"
)
func GetMockRepository(t *testing.T, db *db.Database) *Repository {
logger := util.NewLogger()
wsEventManager := events.NewWSEventManager(logger)
anilistClient := anilist.TestGetMockAnilistClient()
anilistClientRef := util.NewRef(anilistClient)
extensionBankRef := util.NewRef(extension.NewUnifiedBank())
platform := anilist_platform.NewAnilistPlatform(anilistClientRef, extensionBankRef, logger, db)
metadataProvider := metadata_provider.GetFakeProvider(t, db)
platformRef := util.NewRef(platform)
metadataProviderRef := util.NewRef(metadataProvider)
playbackManager := playbackmanager.New(&playbackmanager.NewPlaybackManagerOptions{
WSEventManager: wsEventManager,
Logger: logger,
PlatformRef: platformRef,
MetadataProviderRef: metadataProviderRef,
Database: db,
RefreshAnimeCollectionFunc: func() {
// Do nothing
},
DiscordPresence: nil,
IsOfflineRef: util.NewRef(false),
ContinuityManager: continuity.GetMockManager(t, db),
})
r := NewRepository(&NewRepositoryOptions{
Logger: logger,
WSEventManager: wsEventManager,
Database: db,
MetadataProviderRef: metadataProviderRef,
PlatformRef: platformRef,
PlaybackManager: playbackManager,
})
return r
}

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path/filepath"
"seanime/internal/testutil"
"testing"
"github.com/stretchr/testify/require"
@@ -30,6 +31,20 @@ func PrintPathStructure(path string, indent string) error {
return nil
}
func writeFixtureFile(t testing.TB, root string, fixturePath string) string {
t.Helper()
target := filepath.Join(root, testutil.FixtureRelPath(fixturePath))
if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
t.Fatalf("failed to create directory: %v", err)
}
if err := os.WriteFile(target, []byte("dummy content"), 0644); err != nil {
t.Fatalf("failed to create file %s: %v", target, err)
}
return target
}
func TestCreateTempDir(t *testing.T) {
files := []string{
@@ -39,13 +54,7 @@ func TestCreateTempDir(t *testing.T) {
root := t.TempDir()
for _, file := range files {
path := filepath.Join(root, file)
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
t.Fatalf("failed to create directory: %v", err)
}
if err := os.WriteFile(path, []byte("dummy content"), 0644); err != nil {
t.Fatalf("failed to create file %s: %v", path, err)
}
writeFixtureFile(t, root, file)
}
defer os.RemoveAll(root)
@@ -172,13 +181,7 @@ func TestMoveContentsTo(t *testing.T) {
// Create the source directory structure
root := t.TempDir()
for _, file := range tt.files {
path := filepath.Join(root, file)
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
t.Fatalf("failed to create directory: %v", err)
}
if err := os.WriteFile(path, []byte("dummy content"), 0644); err != nil {
t.Fatalf("failed to create file %s: %v", path, err)
}
writeFixtureFile(t, root, file)
}
PrintPathStructure(root, "")

View File

@@ -6,7 +6,6 @@ import (
"os"
"seanime/internal/api/anilist"
"seanime/internal/api/metadata_provider"
"seanime/internal/database/db"
"seanime/internal/extension"
"seanime/internal/platforms/anilist_platform"
"seanime/internal/testutil"
@@ -17,18 +16,18 @@ import (
)
func TestGojaAnimeTorrentProvider(t *testing.T) {
cfg := testutil.InitTestProvider(t, testutil.Anilist())
env := testutil.NewTestEnv(t)
logger := util.NewLogger()
database, _ := db.NewDatabase(cfg.Path.DataDir, cfg.Database.Name, logger)
database := env.MustNewDatabase(logger)
anilistClient := anilist.TestGetMockAnilistClient()
anilistClient := anilist.NewTestAnilistClient()
anilistClientRef := util.NewRef(anilistClient)
extensionBankRef := util.NewRef(extension.NewUnifiedBank())
platform := anilist_platform.NewAnilistPlatform(anilistClientRef, extensionBankRef, logger, database)
platformRef := util.NewRef(platform)
metadataProvider := metadata_provider.GetFakeProvider(t, database)
metadataProvider := metadata_provider.NewTestProvider(t, database)
metadataProviderRef := util.NewRef(metadataProvider)
repo := NewPlaygroundRepository(logger, platformRef, metadataProviderRef)

View File

@@ -4,10 +4,10 @@ import (
"fmt"
"net/http"
"net/http/httptest"
"os"
"seanime/internal/api/anilist"
"seanime/internal/api/metadata_provider"
"seanime/internal/continuity"
"seanime/internal/database/db"
"seanime/internal/events"
"seanime/internal/extension"
"seanime/internal/goja/goja_runtime"
@@ -30,14 +30,24 @@ import (
"github.com/stretchr/testify/require"
)
// TODO(Test): Replace with in-memory stubs
var (
testDocumentsDir = "/Users/rahim/Documents"
testDocumentCollectionDir = "/Users/rahim/Documents/collection"
testVideoPath = "/Users/rahim/Documents/collection/Bocchi the Rock/[ASW] Bocchi the Rock! - 01 [1080p HEVC][EDC91675].mkv"
type pluginTestPaths struct {
DocumentsDir string
CollectionDir string
}
tempTestDir = "$TEMP/test"
)
func newPluginTestPaths(t testing.TB) pluginTestPaths {
t.Helper()
env := testutil.NewTestEnv(t)
documentsDir := env.MustMkdir("Documents")
collectionDir := env.MustMkdir("Documents", "collection")
env.MustWriteFixtureFile("/Documents/collection/fixture.txt", []byte("fixture"))
return pluginTestPaths{
DocumentsDir: documentsDir,
CollectionDir: collectionDir,
}
}
// TestPluginOptions contains options for initializing a test plugin
type TestPluginOptions struct {
@@ -63,9 +73,9 @@ func DefaultTestPluginOptions() TestPluginOptions {
// InitTestPlugin initializes a test plugin with the given options
func InitTestPlugin(t testing.TB, opts TestPluginOptions) (*GojaPlugin, *zerolog.Logger, *goja_runtime.Manager, *anilist_platform.AnilistPlatform, events.WSEventManagerInterface, error) {
cfg := testutil.LoadConfig(t)
env := testutil.NewTestEnv(t)
if opts.SetupHooks {
cfg = testutil.InitTestProvider(t, testutil.Anilist())
env = testutil.NewTestEnv(t, testutil.Anilist())
}
ext := &extension.Extension{
@@ -84,10 +94,9 @@ func InitTestPlugin(t testing.TB, opts TestPluginOptions) (*GojaPlugin, *zerolog
ext.Plugin.Permissions.Allow = opts.Permissions.Allow
logger := util.NewLogger()
database, err := db.NewDatabase(cfg.Path.DataDir, cfg.Database.Name, logger)
require.NoError(t, err)
database := env.MustNewDatabase(logger)
wsEventManager := events.NewMockWSEventManager(logger)
anilistClientRef := util.NewRef[anilist.AnilistClient](anilist.NewMockAnilistClient())
anilistClientRef := util.NewRef[anilist.AnilistClient](anilist.NewFixtureAnilistClient())
extensionBankRef := util.NewRef(extension.NewUnifiedBank())
anilistPlatform := anilist_platform.NewAnilistPlatform(anilistClientRef, extensionBankRef, logger, database).(*anilist_platform.AnilistPlatform)
anilistPlatformRef := util.NewRef[platform.Platform](anilistPlatform)
@@ -141,7 +150,6 @@ func TestGojaPluginAnime(t *testing.T) {
})
}
`
opts := DefaultTestPluginOptions()
opts.Payload = payload
opts.Permissions = extension.PluginPermissions{
@@ -150,10 +158,9 @@ func TestGojaPluginAnime(t *testing.T) {
extension.PluginPermissionDatabase,
},
}
cfg := testutil.InitTestProvider(t, testutil.Anilist())
env := testutil.NewTestEnv(t, testutil.Anilist())
logger := util.NewLogger()
database, err := db.NewDatabase(cfg.Path.DataDir, cfg.Database.Name, logger)
require.NoError(t, err)
database := env.MustNewDatabase(logger)
metadataProvider := metadata_provider.NewProvider(&metadata_provider.NewProviderImplOptions{
Logger: logger,
@@ -185,6 +192,9 @@ func TestGojaPluginAnime(t *testing.T) {
/////////////////////////////////////////////////////////////////////////////////////////////
func TestGojaPluginMpv(t *testing.T) {
testutil.InitTestProvider(t, testutil.MediaPlayer())
sampleVideoPath := testutil.RequireSampleVideoPath(t)
payload := fmt.Sprintf(`
function init() {
@@ -213,7 +223,7 @@ function init() {
});
}
`, testVideoPath)
`, sampleVideoPath)
playbackManager, _, err := getPlaybackManager(t)
require.NoError(t, err)
@@ -243,18 +253,18 @@ function init() {
// Test that the plugin cannot access paths that are not allowed
// $os.readDir should throw an error
func TestGojaPluginPathNotAllowed(t *testing.T) {
paths := newPluginTestPaths(t)
homeDir, err := os.UserHomeDir()
require.NoError(t, err)
payload := fmt.Sprintf(`
function init() {
$ui.register((ctx) => {
const tempDir = $os.tempDir();
console.log("Temp dir", tempDir);
const dirPath = "%s";
const entries = $os.readDir(dirPath);
const dirPath = %q;
$os.readDir(dirPath);
});
}
`, testDocumentCollectionDir)
`, homeDir)
opts := DefaultTestPluginOptions()
opts.Payload = payload
@@ -263,7 +273,7 @@ function init() {
extension.PluginPermissionSystem,
},
Allow: extension.PluginAllowlist{
ReadPaths: []string{"$TEMP/*", testDocumentsDir},
ReadPaths: []string{paths.DocumentsDir},
WritePaths: []string{"$TEMP/*"},
},
}
@@ -279,6 +289,9 @@ function init() {
// Test that the plugin can play a video and listen to events
func TestGojaPluginPlaybackEvents(t *testing.T) {
testutil.InitTestProvider(t, testutil.MediaPlayer())
sampleVideoPath := testutil.RequireSampleVideoPath(t)
payload := fmt.Sprintf(`
function init() {
@@ -298,7 +311,7 @@ function init() {
});
}
`, testVideoPath)
`, sampleVideoPath)
playbackManager, _, err := getPlaybackManager(t)
require.NoError(t, err)
@@ -801,25 +814,21 @@ func TestGojaPluginStorage2(t *testing.T) {
/////////////////////////////////////////////////////////////////////////////////////////////s
func getPlaybackManager(t *testing.T) (*playbackmanager.PlaybackManager, *anilist.AnimeCollection, error) {
cfg := testutil.LoadConfig(t)
env := testutil.NewTestEnv(t)
logger := util.NewLogger()
wsEventManager := events.NewMockWSEventManager(logger)
database, err := db.NewDatabase(cfg.Path.DataDir, cfg.Database.Name, logger)
if err != nil {
t.Fatalf("error while creating database, %v", err)
}
database := env.MustNewDatabase(logger)
filecacher, err := filecache.NewCacher(t.TempDir())
require.NoError(t, err)
anilistClient := anilist.TestGetMockAnilistClient()
anilistClient := anilist.NewTestAnilistClient()
anilistClientRef := util.NewRef(anilistClient)
anilistPlatform := anilist_platform.NewAnilistPlatform(anilistClientRef, util.NewRef(extension.NewUnifiedBank()), logger, database)
animeCollection, err := anilistPlatform.GetAnimeCollection(t.Context(), true)
metadataProvider := metadata_provider.GetFakeProvider(t, database)
metadataProvider := metadata_provider.NewTestProvider(t, database)
require.NoError(t, err)
continuityManager := continuity.NewManager(&continuity.NewManagerOptions{
FileCacher: filecacher,

View File

@@ -7,7 +7,6 @@ import (
"seanime/internal/extension"
"seanime/internal/library/anime"
"seanime/internal/platforms/anilist_platform"
"seanime/internal/testutil"
"seanime/internal/util"
"testing"
@@ -15,16 +14,15 @@ import (
)
func TestNewLibraryCollection(t *testing.T) {
testutil.InitTestProvider(t, testutil.Anilist())
logger := util.NewLogger()
database, err := db.NewDatabase(t.TempDir(), "test", logger)
assert.NoError(t, err)
metadataProvider := metadata_provider.GetFakeProvider(t, database)
metadataProvider := metadata_provider.NewTestProvider(t, database)
//wsEventManager := events.NewMockWSEventManager(logger)
anilistClient := anilist.TestGetMockAnilistClient()
anilistClient := anilist.NewTestAnilistClient()
anilistPlatform := anilist_platform.NewAnilistPlatform(util.NewRef(anilistClient), util.NewRef(extension.NewUnifiedBank()), logger, database)
animeCollection, err := anilistPlatform.GetAnimeCollection(t.Context(), false)
@@ -38,18 +36,23 @@ func TestNewLibraryCollection(t *testing.T) {
// Sousou no Frieren
// 7 episodes downloaded, 4 watched
mediaId := 154587
lfs = append(lfs, anime.MockHydratedLocalFiles(
anime.MockGenerateHydratedLocalFileGroupOptions("E:/Anime", "E:\\Anime\\Sousou no Frieren\\[SubsPlease] Sousou no Frieren - %ep (1080p) [F02B9CEE].mkv", mediaId, []anime.MockHydratedLocalFileWrapperOptionsMetadata{
{MetadataEpisode: 1, MetadataAniDbEpisode: "1", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 2, MetadataAniDbEpisode: "2", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 3, MetadataAniDbEpisode: "3", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 4, MetadataAniDbEpisode: "4", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 5, MetadataAniDbEpisode: "5", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 6, MetadataAniDbEpisode: "6", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 7, MetadataAniDbEpisode: "7", MetadataType: anime.LocalFileTypeMain},
}),
lfs = append(lfs, anime.NewTestLocalFiles(
anime.TestLocalFileGroup{
LibraryPath: "E:/Anime",
FilePathTemplate: "E:\\Anime\\Sousou no Frieren\\[SubsPlease] Sousou no Frieren - %ep (1080p) [F02B9CEE].mkv",
MediaID: mediaId,
Episodes: []anime.TestLocalFileEpisode{
{Episode: 1, AniDBEpisode: "1", Type: anime.LocalFileTypeMain},
{Episode: 2, AniDBEpisode: "2", Type: anime.LocalFileTypeMain},
{Episode: 3, AniDBEpisode: "3", Type: anime.LocalFileTypeMain},
{Episode: 4, AniDBEpisode: "4", Type: anime.LocalFileTypeMain},
{Episode: 5, AniDBEpisode: "5", Type: anime.LocalFileTypeMain},
{Episode: 6, AniDBEpisode: "6", Type: anime.LocalFileTypeMain},
{Episode: 7, AniDBEpisode: "7", Type: anime.LocalFileTypeMain},
},
},
)...)
anilist.TestModifyAnimeCollectionEntry(animeCollection, mediaId, anilist.TestModifyAnimeCollectionEntryInput{
anilist.PatchAnimeCollectionEntry(animeCollection, mediaId, anilist.AnimeCollectionEntryPatch{
Status: new(anilist.MediaListStatusCurrent),
Progress: new(4), // Mock progress
})
@@ -57,30 +60,40 @@ func TestNewLibraryCollection(t *testing.T) {
// One Piece
// Downloaded 1070-1075 but only watched up until 1060
mediaId = 21
lfs = append(lfs, anime.MockHydratedLocalFiles(
anime.MockGenerateHydratedLocalFileGroupOptions("E:/Anime", "E:\\Anime\\One Piece\\[SubsPlease] One Piece - %ep (1080p) [F02B9CEE].mkv", mediaId, []anime.MockHydratedLocalFileWrapperOptionsMetadata{
{MetadataEpisode: 1070, MetadataAniDbEpisode: "1070", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 1071, MetadataAniDbEpisode: "1071", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 1072, MetadataAniDbEpisode: "1072", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 1073, MetadataAniDbEpisode: "1073", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 1074, MetadataAniDbEpisode: "1074", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 1075, MetadataAniDbEpisode: "1075", MetadataType: anime.LocalFileTypeMain},
}),
lfs = append(lfs, anime.NewTestLocalFiles(
anime.TestLocalFileGroup{
LibraryPath: "E:/Anime",
FilePathTemplate: "E:\\Anime\\One Piece\\[SubsPlease] One Piece - %ep (1080p) [F02B9CEE].mkv",
MediaID: mediaId,
Episodes: []anime.TestLocalFileEpisode{
{Episode: 1070, AniDBEpisode: "1070", Type: anime.LocalFileTypeMain},
{Episode: 1071, AniDBEpisode: "1071", Type: anime.LocalFileTypeMain},
{Episode: 1072, AniDBEpisode: "1072", Type: anime.LocalFileTypeMain},
{Episode: 1073, AniDBEpisode: "1073", Type: anime.LocalFileTypeMain},
{Episode: 1074, AniDBEpisode: "1074", Type: anime.LocalFileTypeMain},
{Episode: 1075, AniDBEpisode: "1075", Type: anime.LocalFileTypeMain},
},
},
)...)
anilist.TestModifyAnimeCollectionEntry(animeCollection, mediaId, anilist.TestModifyAnimeCollectionEntryInput{
anilist.PatchAnimeCollectionEntry(animeCollection, mediaId, anilist.AnimeCollectionEntryPatch{
Status: new(anilist.MediaListStatusCurrent),
Progress: new(1060), // Mock progress
})
// Add unmatched local files
mediaId = 0
lfs = append(lfs, anime.MockHydratedLocalFiles(
anime.MockGenerateHydratedLocalFileGroupOptions("E:/Anime", "E:\\Anime\\Unmatched\\[SubsPlease] Unmatched - %ep (1080p) [F02B9CEE].mkv", mediaId, []anime.MockHydratedLocalFileWrapperOptionsMetadata{
{MetadataEpisode: 1, MetadataAniDbEpisode: "1", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 2, MetadataAniDbEpisode: "2", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 3, MetadataAniDbEpisode: "3", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 4, MetadataAniDbEpisode: "4", MetadataType: anime.LocalFileTypeMain},
}),
lfs = append(lfs, anime.NewTestLocalFiles(
anime.TestLocalFileGroup{
LibraryPath: "E:/Anime",
FilePathTemplate: "E:\\Anime\\Unmatched\\[SubsPlease] Unmatched - %ep (1080p) [F02B9CEE].mkv",
MediaID: mediaId,
Episodes: []anime.TestLocalFileEpisode{
{Episode: 1, AniDBEpisode: "1", Type: anime.LocalFileTypeMain},
{Episode: 2, AniDBEpisode: "2", Type: anime.LocalFileTypeMain},
{Episode: 3, AniDBEpisode: "3", Type: anime.LocalFileTypeMain},
{Episode: 4, AniDBEpisode: "4", Type: anime.LocalFileTypeMain},
},
},
)...)
libraryCollection, err := anime.NewLibraryCollection(t.Context(), &anime.NewLibraryCollectionOptions{

View File

@@ -5,7 +5,6 @@ import (
"seanime/internal/api/anilist"
"seanime/internal/api/metadata"
"seanime/internal/api/metadata_provider"
"seanime/internal/database/db"
"seanime/internal/library/anime"
"seanime/internal/testutil"
"seanime/internal/util"
@@ -16,15 +15,14 @@ import (
)
func TestNewEntryDownloadInfo(t *testing.T) {
cfg := testutil.InitTestProvider(t, testutil.Anilist())
env := testutil.NewTestEnv(t)
logger := util.NewLogger()
database, err := db.NewDatabase(cfg.Path.DataDir, cfg.Database.Name, logger)
require.NoError(t, err)
metadataProvider := metadata_provider.GetFakeProvider(t, database)
database := env.MustNewDatabase(logger)
metadataProvider := metadata_provider.NewTestProviderWithEnv(env, database)
anilistClient := anilist.TestGetMockAnilistClient()
animeCollection, err := anilistClient.AnimeCollection(context.Background(), &cfg.Provider.AnilistUsername)
anilistClient := anilist.NewTestAnilistClient()
animeCollection, err := anilistClient.AnimeCollection(context.Background(), nil)
if err != nil {
t.Fatal(err)
}
@@ -157,16 +155,14 @@ func TestNewEntryDownloadInfo(t *testing.T) {
}
func TestNewEntryDownloadInfo2(t *testing.T) {
cfg := testutil.InitTestProvider(t, testutil.Anilist())
mediaId := 21
env := testutil.NewTestEnv(t)
logger := util.NewLogger()
database, err := db.NewDatabase(cfg.Path.DataDir, cfg.Database.Name, logger)
require.NoError(t, err)
metadataProvider := metadata_provider.GetFakeProvider(t, database)
database := env.MustNewDatabase(logger)
metadataProvider := metadata_provider.NewTestProviderWithEnv(env, database)
anilistClient := anilist.TestGetMockAnilistClient()
anilistClient := anilist.NewTestAnilistClient()
animeCollection, err := anilistClient.AnimeCollection(context.Background(), nil)
if err != nil {
t.Fatal(err)

View File

@@ -7,7 +7,6 @@ import (
"seanime/internal/extension"
"seanime/internal/library/anime"
"seanime/internal/platforms/anilist_platform"
"seanime/internal/testutil"
"seanime/internal/util"
"testing"
@@ -17,13 +16,12 @@ import (
// TestNewAnimeEntry tests /library/entry endpoint.
// /!\ MAKE SURE TO HAVE THE MEDIA ADDED TO YOUR LIST TEST ACCOUNT LISTS
func TestNewAnimeEntry(t *testing.T) {
testutil.InitTestProvider(t, testutil.Anilist())
logger := util.NewLogger()
database, err := db.NewDatabase(t.TempDir(), "test", logger)
assert.NoError(t, err)
metadataProvider := metadata_provider.GetFakeProvider(t, database)
metadataProvider := metadata_provider.NewTestProvider(t, database)
tests := []struct {
name string
@@ -36,14 +34,19 @@ func TestNewAnimeEntry(t *testing.T) {
{
name: "Sousou no Frieren",
mediaId: 154587,
localFiles: anime.MockHydratedLocalFiles(
anime.MockGenerateHydratedLocalFileGroupOptions("E:/Anime", "E:\\Anime\\Sousou no Frieren\\[SubsPlease] Sousou no Frieren - %ep (1080p) [F02B9CEE].mkv", 154587, []anime.MockHydratedLocalFileWrapperOptionsMetadata{
{MetadataEpisode: 1, MetadataAniDbEpisode: "1", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 2, MetadataAniDbEpisode: "2", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 3, MetadataAniDbEpisode: "3", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 4, MetadataAniDbEpisode: "4", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 5, MetadataAniDbEpisode: "5", MetadataType: anime.LocalFileTypeMain},
}),
localFiles: anime.NewTestLocalFiles(
anime.TestLocalFileGroup{
LibraryPath: "E:/Anime",
FilePathTemplate: "E:\\Anime\\Sousou no Frieren\\[SubsPlease] Sousou no Frieren - %ep (1080p) [F02B9CEE].mkv",
MediaID: 154587,
Episodes: []anime.TestLocalFileEpisode{
{Episode: 1, AniDBEpisode: "1", Type: anime.LocalFileTypeMain},
{Episode: 2, AniDBEpisode: "2", Type: anime.LocalFileTypeMain},
{Episode: 3, AniDBEpisode: "3", Type: anime.LocalFileTypeMain},
{Episode: 4, AniDBEpisode: "4", Type: anime.LocalFileTypeMain},
{Episode: 5, AniDBEpisode: "5", Type: anime.LocalFileTypeMain},
},
},
),
currentProgress: 4,
expectedNextEpisodeNumber: 5,
@@ -52,22 +55,27 @@ func TestNewAnimeEntry(t *testing.T) {
{
name: "Mushoku Tensei II Isekai Ittara Honki Dasu",
mediaId: 146065,
localFiles: anime.MockHydratedLocalFiles(
anime.MockGenerateHydratedLocalFileGroupOptions("E:/Anime", "E:/Anime/Mushoku Tensei II Isekai Ittara Honki Dasu/[SubsPlease] Mushoku Tensei S2 - 00 (1080p) [9C362DC3].mkv", 146065, []anime.MockHydratedLocalFileWrapperOptionsMetadata{
{MetadataEpisode: 0, MetadataAniDbEpisode: "S1", MetadataType: anime.LocalFileTypeMain}, // Special episode
{MetadataEpisode: 1, MetadataAniDbEpisode: "1", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 2, MetadataAniDbEpisode: "2", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 3, MetadataAniDbEpisode: "3", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 4, MetadataAniDbEpisode: "4", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 5, MetadataAniDbEpisode: "5", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 6, MetadataAniDbEpisode: "6", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 7, MetadataAniDbEpisode: "7", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 8, MetadataAniDbEpisode: "8", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 9, MetadataAniDbEpisode: "9", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 10, MetadataAniDbEpisode: "10", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 11, MetadataAniDbEpisode: "11", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 12, MetadataAniDbEpisode: "12", MetadataType: anime.LocalFileTypeMain},
}),
localFiles: anime.NewTestLocalFiles(
anime.TestLocalFileGroup{
LibraryPath: "E:/Anime",
FilePathTemplate: "E:/Anime/Mushoku Tensei II Isekai Ittara Honki Dasu/[SubsPlease] Mushoku Tensei S2 - 00 (1080p) [9C362DC3].mkv",
MediaID: 146065,
Episodes: []anime.TestLocalFileEpisode{
{Episode: 0, AniDBEpisode: "S1", Type: anime.LocalFileTypeMain},
{Episode: 1, AniDBEpisode: "1", Type: anime.LocalFileTypeMain},
{Episode: 2, AniDBEpisode: "2", Type: anime.LocalFileTypeMain},
{Episode: 3, AniDBEpisode: "3", Type: anime.LocalFileTypeMain},
{Episode: 4, AniDBEpisode: "4", Type: anime.LocalFileTypeMain},
{Episode: 5, AniDBEpisode: "5", Type: anime.LocalFileTypeMain},
{Episode: 6, AniDBEpisode: "6", Type: anime.LocalFileTypeMain},
{Episode: 7, AniDBEpisode: "7", Type: anime.LocalFileTypeMain},
{Episode: 8, AniDBEpisode: "8", Type: anime.LocalFileTypeMain},
{Episode: 9, AniDBEpisode: "9", Type: anime.LocalFileTypeMain},
{Episode: 10, AniDBEpisode: "10", Type: anime.LocalFileTypeMain},
{Episode: 11, AniDBEpisode: "11", Type: anime.LocalFileTypeMain},
{Episode: 12, AniDBEpisode: "12", Type: anime.LocalFileTypeMain},
},
},
),
currentProgress: 0,
expectedNextEpisodeNumber: 0,
@@ -75,7 +83,7 @@ func TestNewAnimeEntry(t *testing.T) {
},
}
anilistClient := anilist.TestGetMockAnilistClient()
anilistClient := anilist.NewTestAnilistClient()
anilistPlatform := anilist_platform.NewAnilistPlatform(util.NewRef(anilistClient), util.NewRef(extension.NewUnifiedBank()), logger, database)
animeCollection, err := anilistPlatform.GetAnimeCollection(t.Context(), false)
if err != nil {
@@ -86,7 +94,7 @@ func TestNewAnimeEntry(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
anilist.TestModifyAnimeCollectionEntry(animeCollection, tt.mediaId, anilist.TestModifyAnimeCollectionEntryInput{
anilist.PatchAnimeCollectionEntry(animeCollection, tt.mediaId, anilist.AnimeCollectionEntryPatch{
Progress: new(tt.currentProgress), // Mock progress
})

View File

@@ -2,32 +2,48 @@ package anime_test
import (
"cmp"
"github.com/stretchr/testify/assert"
"seanime/internal/library/anime"
"slices"
"testing"
"github.com/stretchr/testify/assert"
)
func TestLocalFileWrapperEntry(t *testing.T) {
lfs := anime.MockHydratedLocalFiles(
anime.MockGenerateHydratedLocalFileGroupOptions("/mnt/anime/", "/mnt/anime/One Piece/One Piece - %ep.mkv", 21, []anime.MockHydratedLocalFileWrapperOptionsMetadata{
{MetadataEpisode: 1070, MetadataAniDbEpisode: "1070", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 1071, MetadataAniDbEpisode: "1071", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 1072, MetadataAniDbEpisode: "1072", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 1073, MetadataAniDbEpisode: "1073", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 1074, MetadataAniDbEpisode: "1074", MetadataType: anime.LocalFileTypeMain},
}),
anime.MockGenerateHydratedLocalFileGroupOptions("/mnt/anime/", "/mnt/anime/Blue Lock/Blue Lock - %ep.mkv", 22222, []anime.MockHydratedLocalFileWrapperOptionsMetadata{
{MetadataEpisode: 1, MetadataAniDbEpisode: "1", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 2, MetadataAniDbEpisode: "2", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 3, MetadataAniDbEpisode: "3", MetadataType: anime.LocalFileTypeMain},
}),
anime.MockGenerateHydratedLocalFileGroupOptions("/mnt/anime/", "/mnt/anime/Kimi ni Todoke/Kimi ni Todoke - %ep.mkv", 9656, []anime.MockHydratedLocalFileWrapperOptionsMetadata{
{MetadataEpisode: 0, MetadataAniDbEpisode: "S1", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 1, MetadataAniDbEpisode: "1", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 2, MetadataAniDbEpisode: "2", MetadataType: anime.LocalFileTypeMain},
}),
lfs := anime.NewTestLocalFiles(
anime.TestLocalFileGroup{
LibraryPath: "/mnt/anime/",
FilePathTemplate: "/mnt/anime/One Piece/One Piece - %ep.mkv",
MediaID: 21,
Episodes: []anime.TestLocalFileEpisode{
{Episode: 1070, AniDBEpisode: "1070", Type: anime.LocalFileTypeMain},
{Episode: 1071, AniDBEpisode: "1071", Type: anime.LocalFileTypeMain},
{Episode: 1072, AniDBEpisode: "1072", Type: anime.LocalFileTypeMain},
{Episode: 1073, AniDBEpisode: "1073", Type: anime.LocalFileTypeMain},
{Episode: 1074, AniDBEpisode: "1074", Type: anime.LocalFileTypeMain},
},
},
anime.TestLocalFileGroup{
LibraryPath: "/mnt/anime/",
FilePathTemplate: "/mnt/anime/Blue Lock/Blue Lock - %ep.mkv",
MediaID: 22222,
Episodes: []anime.TestLocalFileEpisode{
{Episode: 1, AniDBEpisode: "1", Type: anime.LocalFileTypeMain},
{Episode: 2, AniDBEpisode: "2", Type: anime.LocalFileTypeMain},
{Episode: 3, AniDBEpisode: "3", Type: anime.LocalFileTypeMain},
},
},
anime.TestLocalFileGroup{
LibraryPath: "/mnt/anime/",
FilePathTemplate: "/mnt/anime/Kimi ni Todoke/Kimi ni Todoke - %ep.mkv",
MediaID: 9656,
Episodes: []anime.TestLocalFileEpisode{
{Episode: 0, AniDBEpisode: "S1", Type: anime.LocalFileTypeMain},
{Episode: 1, AniDBEpisode: "1", Type: anime.LocalFileTypeMain},
{Episode: 2, AniDBEpisode: "2", Type: anime.LocalFileTypeMain},
},
},
)
tests := []struct {
@@ -100,17 +116,27 @@ func TestLocalFileWrapperEntry(t *testing.T) {
func TestLocalFileWrapperEntryProgressNumber(t *testing.T) {
lfs := anime.MockHydratedLocalFiles(
anime.MockGenerateHydratedLocalFileGroupOptions("/mnt/anime/", "/mnt/anime/Kimi ni Todoke/Kimi ni Todoke - %ep.mkv", 9656, []anime.MockHydratedLocalFileWrapperOptionsMetadata{
{MetadataEpisode: 0, MetadataAniDbEpisode: "S1", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 1, MetadataAniDbEpisode: "1", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 2, MetadataAniDbEpisode: "2", MetadataType: anime.LocalFileTypeMain},
}),
anime.MockGenerateHydratedLocalFileGroupOptions("/mnt/anime/", "/mnt/anime/Kimi ni Todoke/Kimi ni Todoke - %ep.mkv", 9656_2, []anime.MockHydratedLocalFileWrapperOptionsMetadata{
{MetadataEpisode: 1, MetadataAniDbEpisode: "S1", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 2, MetadataAniDbEpisode: "1", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 3, MetadataAniDbEpisode: "2", MetadataType: anime.LocalFileTypeMain},
}),
lfs := anime.NewTestLocalFiles(
anime.TestLocalFileGroup{
LibraryPath: "/mnt/anime/",
FilePathTemplate: "/mnt/anime/Kimi ni Todoke/Kimi ni Todoke - %ep.mkv",
MediaID: 9656,
Episodes: []anime.TestLocalFileEpisode{
{Episode: 0, AniDBEpisode: "S1", Type: anime.LocalFileTypeMain},
{Episode: 1, AniDBEpisode: "1", Type: anime.LocalFileTypeMain},
{Episode: 2, AniDBEpisode: "2", Type: anime.LocalFileTypeMain},
},
},
anime.TestLocalFileGroup{
LibraryPath: "/mnt/anime/",
FilePathTemplate: "/mnt/anime/Kimi ni Todoke/Kimi ni Todoke - %ep.mkv",
MediaID: 9656_2,
Episodes: []anime.TestLocalFileEpisode{
{Episode: 1, AniDBEpisode: "S1", Type: anime.LocalFileTypeMain},
{Episode: 2, AniDBEpisode: "1", Type: anime.LocalFileTypeMain},
{Episode: 3, AniDBEpisode: "2", Type: anime.LocalFileTypeMain},
},
},
)
tests := []struct {

View File

@@ -23,9 +23,9 @@ func TestNewMissingEpisodes(t *testing.T) {
logger := util.NewLogger()
database, _ := db.NewDatabase(t.TempDir(), "test", logger)
metadataProvider := metadata_provider.GetFakeProvider(t, database)
metadataProvider := metadata_provider.NewTestProvider(t, database)
anilistClient := anilist.TestGetMockAnilistClient()
anilistClient := anilist.NewTestAnilistClient()
animeCollection, err := anilistClient.AnimeCollection(context.Background(), nil)
if err != nil {
t.Fatal(err)
@@ -45,14 +45,19 @@ func TestNewMissingEpisodes(t *testing.T) {
// So we should expect to see 5 missing episodes
name: "Sousou no Frieren, missing 5 episodes",
mediaId: 154587,
localFiles: anime.MockHydratedLocalFiles(
anime.MockGenerateHydratedLocalFileGroupOptions("E:/Anime", "E:\\Anime\\Sousou no Frieren\\[SubsPlease] Sousou no Frieren - %ep (1080p) [F02B9CEE].mkv", 154587, []anime.MockHydratedLocalFileWrapperOptionsMetadata{
{MetadataEpisode: 1, MetadataAniDbEpisode: "1", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 2, MetadataAniDbEpisode: "2", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 3, MetadataAniDbEpisode: "3", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 4, MetadataAniDbEpisode: "4", MetadataType: anime.LocalFileTypeMain},
{MetadataEpisode: 5, MetadataAniDbEpisode: "5", MetadataType: anime.LocalFileTypeMain},
}),
localFiles: anime.NewTestLocalFiles(
anime.TestLocalFileGroup{
LibraryPath: "E:/Anime",
FilePathTemplate: "E:\\Anime\\Sousou no Frieren\\[SubsPlease] Sousou no Frieren - %ep (1080p) [F02B9CEE].mkv",
MediaID: 154587,
Episodes: []anime.TestLocalFileEpisode{
{Episode: 1, AniDBEpisode: "1", Type: anime.LocalFileTypeMain},
{Episode: 2, AniDBEpisode: "2", Type: anime.LocalFileTypeMain},
{Episode: 3, AniDBEpisode: "3", Type: anime.LocalFileTypeMain},
{Episode: 4, AniDBEpisode: "4", Type: anime.LocalFileTypeMain},
{Episode: 5, AniDBEpisode: "5", Type: anime.LocalFileTypeMain},
},
},
),
mediaAiredEpisodes: 10,
currentProgress: 4,
@@ -66,7 +71,7 @@ func TestNewMissingEpisodes(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
// Mock Anilist collection
anilist.TestModifyAnimeCollectionEntry(animeCollection, tt.mediaId, anilist.TestModifyAnimeCollectionEntryInput{
anilist.PatchAnimeCollectionEntry(animeCollection, tt.mediaId, anilist.AnimeCollectionEntryPatch{
Progress: new(tt.currentProgress), // Mock progress
AiredEpisodes: new(tt.mediaAiredEpisodes),
NextAiringEpisode: &anilist.BaseAnime_NextAiringEpisode{

View File

@@ -5,76 +5,35 @@ import (
"strings"
)
type MockHydratedLocalFileOptions struct {
FilePath string
LibraryPath string
MediaId int
MetadataEpisode int
MetadataAniDbEpisode string
MetadataType LocalFileType
type TestLocalFileEpisode struct {
Episode int
AniDBEpisode string
Type LocalFileType
}
func MockHydratedLocalFile(opts MockHydratedLocalFileOptions) *LocalFile {
lf := NewLocalFile(opts.FilePath, opts.LibraryPath)
lf.MediaId = opts.MediaId
lf.Metadata = &LocalFileMetadata{
AniDBEpisode: opts.MetadataAniDbEpisode,
Episode: opts.MetadataEpisode,
Type: opts.MetadataType,
}
return lf
type TestLocalFileGroup struct {
LibraryPath string
FilePathTemplate string
MediaID int
Episodes []TestLocalFileEpisode
}
// MockHydratedLocalFiles creates a slice of LocalFiles based on the provided options
//
// Example:
//
// MockHydratedLocalFiles(
// MockHydratedLocalFileOptions{
// FilePath: "/mnt/anime/One Piece/One Piece - 1070.mkv",
// LibraryPath: "/mnt/anime/",
// MetadataEpisode: 1070,
// MetadataAniDbEpisode: "1070",
// MetadataType: LocalFileTypeMain,
// },
// MockHydratedLocalFileOptions{
// ...
// },
// )
func MockHydratedLocalFiles(opts ...[]MockHydratedLocalFileOptions) []*LocalFile {
lfs := make([]*LocalFile, 0, len(opts))
for _, opt := range opts {
for _, o := range opt {
lfs = append(lfs, MockHydratedLocalFile(o))
// NewTestLocalFiles expands one or more local-file groups into hydrated LocalFiles.
// FilePathTemplate replaces each %ep token with the episode number.
func NewTestLocalFiles(groups ...TestLocalFileGroup) []*LocalFile {
localFiles := make([]*LocalFile, 0)
for _, group := range groups {
for _, episode := range group.Episodes {
lf := NewLocalFile(strings.ReplaceAll(group.FilePathTemplate, "%ep", strconv.Itoa(episode.Episode)), group.LibraryPath)
lf.MediaId = group.MediaID
lf.Metadata = &LocalFileMetadata{
AniDBEpisode: episode.AniDBEpisode,
Episode: episode.Episode,
Type: episode.Type,
}
localFiles = append(localFiles, lf)
}
}
return lfs
}
type MockHydratedLocalFileWrapperOptionsMetadata struct {
MetadataEpisode int
MetadataAniDbEpisode string
MetadataType LocalFileType
}
// MockGenerateHydratedLocalFileGroupOptions generates a slice of MockHydratedLocalFileOptions based on a template string and metadata
//
// Example:
//
// MockGenerateHydratedLocalFileGroupOptions("/mnt/anime/", "One Piece/One Piece - %ep.mkv", 21, []MockHydratedLocalFileWrapperOptionsMetadata{
// {MetadataEpisode: 1070, MetadataAniDbEpisode: "1070", MetadataType: LocalFileTypeMain},
// })
func MockGenerateHydratedLocalFileGroupOptions(libraryPath string, template string, mId int, m []MockHydratedLocalFileWrapperOptionsMetadata) []MockHydratedLocalFileOptions {
opts := make([]MockHydratedLocalFileOptions, 0, len(m))
for _, metadata := range m {
opts = append(opts, MockHydratedLocalFileOptions{
FilePath: strings.ReplaceAll(template, "%ep", strconv.Itoa(metadata.MetadataEpisode)),
LibraryPath: libraryPath,
MediaId: mId,
MetadataEpisode: metadata.MetadataEpisode,
MetadataAniDbEpisode: metadata.MetadataAniDbEpisode,
MetadataType: metadata.MetadataType,
})
}
return opts
return localFiles
}

View File

@@ -8,7 +8,6 @@ import (
"seanime/internal/debrid/debrid"
hibiketorrent "seanime/internal/extension/hibike/torrent"
"seanime/internal/library/anime"
"seanime/internal/testutil"
"seanime/internal/torrent_clients/torrent_client"
"seanime/internal/util"
"testing"
@@ -973,9 +972,7 @@ func TestIsProfileValidChecks(t *testing.T) {
}
func TestIntegration(t *testing.T) {
testutil.InitTestProvider(t, testutil.Anilist())
anilistClient := anilist.TestGetMockAnilistClient()
anilistClient := anilist.NewTestAnilistClient()
animeCollection, err := anilistClient.AnimeCollection(context.Background(), nil)
require.NoError(t, err)
@@ -1159,15 +1156,15 @@ func TestIntegration(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create a new fake
fake := &Fake{
harness := &TestHarness{
GetLatestResults: tt.torrents,
SearchResults: tt.torrents,
}
ad := fake.New(t)
ad := harness.New(t)
ad.SetAnimeCollection(animeCollection)
// Add local files to the database
_, err = fake.Database.InsertLocalFiles(&models.LocalFiles{Value: []byte("[]")})
_, err = harness.Database.InsertLocalFiles(&models.LocalFiles{Value: []byte("[]")})
require.NoError(t, err)
// Set user progress
@@ -1213,9 +1210,7 @@ func TestIntegration(t *testing.T) {
}
func TestDelayIntegration(t *testing.T) {
testutil.InitTestProvider(t, testutil.Anilist())
anilistClient := anilist.TestGetMockAnilistClient()
anilistClient := anilist.NewTestAnilistClient()
animeCollection, err := anilistClient.AnimeCollection(context.Background(), nil)
require.NoError(t, err)
@@ -1345,7 +1340,7 @@ func TestDelayIntegration(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fake := &Fake{GetLatestResults: tt.torrents, SearchResults: tt.torrents}
fake := &TestHarness{GetLatestResults: tt.torrents, SearchResults: tt.torrents}
ad := fake.New(t)
ad.SetAnimeCollection(animeCollection)

View File

@@ -17,7 +17,7 @@ import (
func TestComparison(t *testing.T) {
database, _ := db.NewDatabase(t.TempDir(), "test", util.NewLogger())
ad := AutoDownloader{
metadataProviderRef: util.NewRef(metadata_provider.GetFakeProvider(t, database)),
metadataProviderRef: util.NewRef(metadata_provider.NewTestProvider(t, database)),
settings: &models.AutoDownloaderSettings{
EnableSeasonCheck: true,
},
@@ -142,7 +142,7 @@ func TestComparison(t *testing.T) {
func TestComparison2(t *testing.T) {
database, _ := db.NewDatabase(t.TempDir(), "test", util.NewLogger())
ad := AutoDownloader{
metadataProviderRef: util.NewRef(metadata_provider.GetFakeProvider(t, database)),
metadataProviderRef: util.NewRef(metadata_provider.NewTestProvider(t, database)),
settings: &models.AutoDownloaderSettings{
EnableSeasonCheck: true,
},
@@ -239,7 +239,7 @@ func TestComparison2(t *testing.T) {
func TestComparison3(t *testing.T) {
database, _ := db.NewDatabase(t.TempDir(), "test", util.NewLogger())
ad := AutoDownloader{
metadataProviderRef: util.NewRef(metadata_provider.GetFakeProvider(t, database)),
metadataProviderRef: util.NewRef(metadata_provider.NewTestProvider(t, database)),
settings: &models.AutoDownloaderSettings{
EnableSeasonCheck: true,
},

View File

@@ -11,43 +11,40 @@ import (
"seanime/internal/testutil"
"seanime/internal/torrents/torrent"
"seanime/internal/util"
"seanime/internal/util/filecache"
"testing"
"github.com/stretchr/testify/require"
)
type Fake struct {
type TestHarness struct {
SearchResults []*hibiketorrent.AnimeTorrent
GetLatestResults []*hibiketorrent.AnimeTorrent
Database *db.Database
}
type FakeTorrentProvider struct {
fake *Fake
type TestTorrentProvider struct {
harness *TestHarness
}
func (f FakeTorrentProvider) Search(opts hibiketorrent.AnimeSearchOptions) ([]*hibiketorrent.AnimeTorrent, error) {
return f.fake.SearchResults, nil
func (f TestTorrentProvider) Search(opts hibiketorrent.AnimeSearchOptions) ([]*hibiketorrent.AnimeTorrent, error) {
return f.harness.SearchResults, nil
}
func (f FakeTorrentProvider) SmartSearch(opts hibiketorrent.AnimeSmartSearchOptions) ([]*hibiketorrent.AnimeTorrent, error) {
return f.fake.SearchResults, nil
func (f TestTorrentProvider) SmartSearch(opts hibiketorrent.AnimeSmartSearchOptions) ([]*hibiketorrent.AnimeTorrent, error) {
return f.harness.SearchResults, nil
}
func (f FakeTorrentProvider) GetTorrentInfoHash(torrent *hibiketorrent.AnimeTorrent) (string, error) {
func (f TestTorrentProvider) GetTorrentInfoHash(torrent *hibiketorrent.AnimeTorrent) (string, error) {
return torrent.InfoHash, nil
}
func (f FakeTorrentProvider) GetTorrentMagnetLink(torrent *hibiketorrent.AnimeTorrent) (string, error) {
func (f TestTorrentProvider) GetTorrentMagnetLink(torrent *hibiketorrent.AnimeTorrent) (string, error) {
return torrent.MagnetLink, nil
}
func (f FakeTorrentProvider) GetLatest() ([]*hibiketorrent.AnimeTorrent, error) {
return f.fake.GetLatestResults, nil
func (f TestTorrentProvider) GetLatest() ([]*hibiketorrent.AnimeTorrent, error) {
return f.harness.GetLatestResults, nil
}
func (f FakeTorrentProvider) GetSettings() hibiketorrent.AnimeProviderSettings {
func (f TestTorrentProvider) GetSettings() hibiketorrent.AnimeProviderSettings {
return hibiketorrent.AnimeProviderSettings{
CanSmartSearch: false,
SmartSearchFilters: nil,
@@ -56,28 +53,27 @@ func (f FakeTorrentProvider) GetSettings() hibiketorrent.AnimeProviderSettings {
}
}
var _ hibiketorrent.AnimeProvider = (*FakeTorrentProvider)(nil)
var _ hibiketorrent.AnimeProvider = (*TestTorrentProvider)(nil)
func (f *Fake) New(t *testing.T) *AutoDownloader {
cfg := testutil.LoadConfig(t)
func (f *TestHarness) New(t *testing.T) *AutoDownloader {
t.Helper()
env := testutil.NewTestEnv(t)
logger := util.NewLogger()
database, err := db.NewDatabase("", cfg.Database.Name, logger)
require.NoError(t, err)
logger := env.Logger()
database := env.NewDatabase("")
f.Database = database
filecacher, err := filecache.NewCacher(t.TempDir())
require.NoError(t, err)
filecacher := env.NewCacher("autodownloader")
extensionBankRef := util.NewRef(extension.NewUnifiedBank())
// Fake Extension
provider := FakeTorrentProvider{fake: f}
// Test extension
provider := TestTorrentProvider{harness: f}
ext := extension.NewAnimeTorrentProviderExtension(&extension.Extension{
ID: "fake",
Type: extension.TypeAnimeTorrentProvider,
Name: "Fake Provider",
Name: "Test Provider",
}, provider)
extensionBankRef.Get().Set("fake", ext)

View File

@@ -1,16 +0,0 @@
package filesystem
import (
"seanime/internal/util"
"testing"
)
func TestDeleteEmptyDirectories(t *testing.T) {
path := "E:/ANIME_TEST"
RemoveEmptyDirectories(path, util.NewLogger())
t.Log("All empty directories removed successfully.")
}

View File

@@ -2,15 +2,10 @@ package scanner
import (
"seanime/internal/api/anilist"
"seanime/internal/api/metadata_provider"
"seanime/internal/database/db"
"seanime/internal/extension"
"seanime/internal/library/anime"
"seanime/internal/library/summary"
"seanime/internal/platforms/anilist_platform"
"seanime/internal/testutil"
"seanime/internal/platforms/platform"
"seanime/internal/util"
"seanime/internal/util/limiter"
"testing"
"github.com/stretchr/testify/assert"
@@ -18,26 +13,12 @@ import (
)
func TestFileHydrator_HydrateMetadata(t *testing.T) {
cfg := testutil.InitTestProvider(t, testutil.Anilist())
completeAnimeCache := anilist.NewCompleteAnimeCache()
anilistRateLimiter := limiter.NewAnilistLimiter()
logger := util.NewLogger()
database, err := db.NewDatabase(cfg.Path.DataDir, cfg.Database.Name, logger)
require.NoError(t, err)
metadataProvider := metadata_provider.GetFakeProvider(t, database)
anilistClient := anilist.NewAnilistClient(cfg.Provider.AnilistJwt, "")
anilistClientRef := util.NewRef[anilist.AnilistClient](anilistClient)
extensionBankRef := util.NewRef(extension.NewUnifiedBank())
//wsEventManager := events.NewMockWSEventManager(logger)
anilistPlatform := anilist_platform.NewAnilistPlatform(anilistClientRef, extensionBankRef, logger, database)
anilistPlatform.SetUsername(cfg.Provider.AnilistUsername)
animeCollection, err := anilistPlatform.GetAnimeCollectionWithRelations(t.Context())
harness := newScannerFixtureHarness(t)
logger := harness.Logger
animeCollection, err := harness.Platform.GetAnimeCollectionWithRelations(t.Context())
require.NoError(t, err)
require.NotNil(t, animeCollection)
allMedia := animeCollection.GetAllAnime()
tests := []struct {
name string
paths []string
@@ -104,6 +85,14 @@ func TestFileHydrator_HydrateMetadata(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
currentStatus := anilist.MediaListStatusCurrent
anilist.EnsureAnimeCollectionWithRelationsEntry(
animeCollection,
tt.expectedMediaId,
anilist.AnimeCollectionEntryPatch{Status: &currentStatus},
harness.AnilistClient,
)
allMedia := animeCollection.GetAllAnime()
scanLogger, err := NewConsoleScanLogger()
if err != nil {
@@ -114,11 +103,7 @@ func TestFileHydrator_HydrateMetadata(t *testing.T) {
// | Local Files |
// +---------------------+
var lfs []*anime.LocalFile
for _, path := range tt.paths {
lf := anime.NewLocalFile(path, "E:/Anime")
lfs = append(lfs, lf)
}
lfs := harness.LocalFiles(tt.paths...)
// +---------------------+
// | MediaContainer |
@@ -158,10 +143,10 @@ func TestFileHydrator_HydrateMetadata(t *testing.T) {
fh := &FileHydrator{
LocalFiles: lfs,
AllMedia: mc.NormalizedMedia,
CompleteAnimeCache: completeAnimeCache,
PlatformRef: util.NewRef(anilistPlatform),
AnilistRateLimiter: anilistRateLimiter,
MetadataProviderRef: util.NewRef(metadataProvider),
CompleteAnimeCache: harness.CompleteAnimeCache,
PlatformRef: util.NewRef[platform.Platform](harness.Platform),
AnilistRateLimiter: harness.AnilistRateLimiter,
MetadataProviderRef: util.NewRef(harness.MetadataProvider),
Logger: logger,
ScanLogger: scanLogger,
Config: config,

View File

@@ -3,14 +3,9 @@ package scanner
import (
"context"
"seanime/internal/api/anilist"
"seanime/internal/api/metadata_provider"
"seanime/internal/database/db"
"seanime/internal/extension"
"seanime/internal/library/anime"
"seanime/internal/platforms/anilist_platform"
"seanime/internal/testutil"
"seanime/internal/platforms/platform"
"seanime/internal/util"
"seanime/internal/util/limiter"
"testing"
"github.com/stretchr/testify/assert"
@@ -18,7 +13,7 @@ import (
func TestMatcher1(t *testing.T) {
anilistClient := anilist.TestGetMockAnilistClient()
anilistClient := anilist.NewTestAnilistClient()
animeCollection, err := anilistClient.AnimeCollectionWithRelations(context.Background(), nil)
if err != nil {
t.Fatal(err.Error())
@@ -103,15 +98,17 @@ func TestMatcher1(t *testing.T) {
}
func TestMatcher2(t *testing.T) {
cfg := testutil.InitTestProvider(t, testutil.Anilist())
anilistClient := anilist.NewAnilistClient(cfg.Provider.AnilistJwt, "")
animeCollection, err := anilistClient.AnimeCollectionWithRelations(context.Background(), &cfg.Provider.AnilistUsername)
harness := newScannerLiveHarness(t)
anilistClient := harness.AnilistClient
animeCollection, err := harness.Platform.GetAnimeCollectionWithRelations(t.Context())
if err != nil {
t.Fatal(err.Error())
}
if animeCollection == nil {
t.Fatal("expected anime collection, got nil")
}
dir := "E:/Anime"
dir := harness.LibraryDir
tests := []struct {
name string
@@ -156,34 +153,12 @@ func TestMatcher2(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Add media to collection if it doesn't exist
allMedia := animeCollection.GetAllAnime()
hasExpectedMediaId := false
for _, media := range allMedia {
if media.ID == tt.expectedMediaId {
hasExpectedMediaId = true
break
}
}
if !hasExpectedMediaId {
anilist.TestAddAnimeCollectionWithRelationsEntry(animeCollection, tt.expectedMediaId, anilist.TestModifyAnimeCollectionEntryInput{Status: new(anilist.MediaListStatusCurrent)}, anilistClient)
allMedia = animeCollection.GetAllAnime()
}
currentStatus := anilist.MediaListStatusCurrent
anilist.EnsureAnimeCollectionWithRelationsEntry(animeCollection, tt.expectedMediaId, anilist.AnimeCollectionEntryPatch{Status: &currentStatus}, anilistClient)
for _, otherMediaId := range tt.otherMediaIds {
hasOtherMediaId := false
for _, media := range allMedia {
if media.ID == otherMediaId {
hasOtherMediaId = true
break
}
}
if !hasOtherMediaId {
anilist.TestAddAnimeCollectionWithRelationsEntry(animeCollection, otherMediaId, anilist.TestModifyAnimeCollectionEntryInput{Status: new(anilist.MediaListStatusCurrent)}, anilistClient)
allMedia = animeCollection.GetAllAnime()
}
anilist.EnsureAnimeCollectionWithRelationsEntry(animeCollection, otherMediaId, anilist.AnimeCollectionEntryPatch{Status: &currentStatus}, anilistClient)
}
allMedia := animeCollection.GetAllAnime()
scanLogger, err := NewConsoleScanLogger()
if err != nil {
@@ -238,15 +213,17 @@ func TestMatcher2(t *testing.T) {
}
func TestMatcher3(t *testing.T) {
cfg := testutil.InitTestProvider(t, testutil.Anilist())
anilistClient := anilist.NewAnilistClient(cfg.Provider.AnilistJwt, "")
animeCollection, err := anilistClient.AnimeCollectionWithRelations(context.Background(), &cfg.Provider.AnilistUsername)
harness := newScannerLiveHarness(t)
anilistClient := harness.AnilistClient
animeCollection, err := harness.Platform.GetAnimeCollectionWithRelations(t.Context())
if err != nil {
t.Fatal(err.Error())
}
if animeCollection == nil {
t.Fatal("expected anime collection, got nil")
}
dir := "E:/Anime"
dir := harness.LibraryDir
tests := []struct {
name string
@@ -856,37 +833,12 @@ func TestMatcher3(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Add media to collection if it doesn't exist
allMedia := animeCollection.GetAllAnime()
// Helper to ensure media exists in collection
hasMedia := false
for _, media := range allMedia {
if media.ID == tt.expectedMediaId {
hasMedia = true
break
}
}
if !hasMedia {
anilist.TestAddAnimeCollectionWithRelationsEntry(animeCollection, tt.expectedMediaId, anilist.TestModifyAnimeCollectionEntryInput{Status: new(anilist.MediaListStatusCurrent)}, anilistClient)
allMedia = animeCollection.GetAllAnime()
}
// Ensure other media exists
currentStatus := anilist.MediaListStatusCurrent
anilist.EnsureAnimeCollectionWithRelationsEntry(animeCollection, tt.expectedMediaId, anilist.AnimeCollectionEntryPatch{Status: &currentStatus}, anilistClient)
for _, id := range tt.otherMediaIds {
hasMedia := false
for _, media := range allMedia {
if media.ID == id {
hasMedia = true
break
}
}
if !hasMedia {
anilist.TestAddAnimeCollectionWithRelationsEntry(animeCollection, id, anilist.TestModifyAnimeCollectionEntryInput{Status: new(anilist.MediaListStatusCurrent)}, anilistClient)
allMedia = animeCollection.GetAllAnime()
}
anilist.EnsureAnimeCollectionWithRelationsEntry(animeCollection, id, anilist.AnimeCollectionEntryPatch{Status: &currentStatus}, anilistClient)
}
allMedia := animeCollection.GetAllAnime()
scanLogger, err := NewConsoleScanLogger()
if err != nil {
@@ -948,41 +900,27 @@ func TestMatcherWithOfflineDB(t *testing.T) {
t.Skip("Skipping integration test")
}
cfg := testutil.InitTestProvider(t, testutil.Anilist())
anilistClient := anilist.TestGetMockAnilistClient()
logger := util.NewLogger()
database, err := db.NewDatabase(cfg.Path.DataDir, cfg.Database.Name, logger)
if err != nil {
t.Fatal(err)
}
anilistClientRef := util.NewRef(anilistClient)
extensionBankRef := util.NewRef(extension.NewUnifiedBank())
anilistPlatform := anilist_platform.NewAnilistPlatform(anilistClientRef, extensionBankRef, logger, database)
anilistPlatform.SetUsername(cfg.Provider.AnilistUsername)
metadataProvider := metadata_provider.GetFakeProvider(t, database)
completeAnimeCache := anilist.NewCompleteAnimeCache()
anilistRateLimiter := limiter.NewAnilistLimiter()
harness := newScannerFixtureHarness(t)
logger := harness.Logger
scanLogger, err := NewConsoleScanLogger()
if err != nil {
t.Fatal("expected result, got error:", err.Error())
}
dir := "E:/Anime"
dir := harness.LibraryDir
t.Log("Initializing MediaFetcher with anime-offline-database...")
mf, err := NewMediaFetcher(t.Context(), &MediaFetcherOptions{
Enhanced: true,
EnhanceWithOfflineDatabase: true, // Use offline database
PlatformRef: util.NewRef(anilistPlatform),
PlatformRef: util.NewRef[platform.Platform](harness.Platform),
LocalFiles: []*anime.LocalFile{}, // Empty, we don't need local files for fetching
CompleteAnimeCache: completeAnimeCache,
MetadataProviderRef: util.NewRef(metadataProvider),
CompleteAnimeCache: harness.CompleteAnimeCache,
MetadataProviderRef: util.NewRef(harness.MetadataProvider),
Logger: logger,
AnilistRateLimiter: anilistRateLimiter,
AnilistRateLimiter: harness.AnilistRateLimiter,
ScanLogger: scanLogger,
DisableAnimeCollection: true, // Only use offline database
})
@@ -1395,15 +1333,17 @@ func TestGetFileFormatType(t *testing.T) {
}
func TestMatcher_applyMatchingRule(t *testing.T) {
cfg := testutil.InitTestProvider(t, testutil.Anilist())
anilistClient := anilist.NewAnilistClient(cfg.Provider.AnilistJwt, "")
animeCollection, err := anilistClient.AnimeCollectionWithRelations(context.Background(), &cfg.Provider.AnilistUsername)
harness := newScannerLiveHarness(t)
anilistClient := harness.AnilistClient
animeCollection, err := harness.Platform.GetAnimeCollectionWithRelations(t.Context())
if err != nil {
t.Fatal(err.Error())
}
if animeCollection == nil {
t.Fatal("expected anime collection, got nil")
}
dir := "E:/Anime"
dir := harness.LibraryDir
tests := []struct {
name string
@@ -1530,34 +1470,17 @@ func TestMatcher_applyMatchingRule(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
// Add medias to collection if it doesn't exist
allMedia := animeCollection.GetAllAnime()
expectedIDs := make([]int, len(tt.expectedMediaIds))
copy(expectedIDs, tt.expectedMediaIds)
for _, media := range allMedia {
for i, expectedID := range expectedIDs {
if media.ID == expectedID {
last := len(expectedIDs) - 1
expectedIDs[i] = expectedIDs[last]
expectedIDs = expectedIDs[:last]
break
}
}
}
for _, missingID := range expectedIDs {
anilist.TestAddAnimeCollectionWithRelationsEntry(
currentStatus := anilist.MediaListStatusCurrent
for _, expectedID := range tt.expectedMediaIds {
anilist.EnsureAnimeCollectionWithRelationsEntry(
animeCollection,
missingID,
anilist.TestModifyAnimeCollectionEntryInput{
Status: new(anilist.MediaListStatusCurrent),
},
expectedID,
anilist.AnimeCollectionEntryPatch{Status: &currentStatus},
anilistClient,
)
}
allMedia = animeCollection.GetAllAnime()
allMedia := animeCollection.GetAllAnime()
scanLogger, err := NewConsoleScanLogger()
if err != nil {

View File

@@ -2,12 +2,7 @@ package scanner
import (
"seanime/internal/api/anilist"
"seanime/internal/api/metadata_provider"
"seanime/internal/database/db"
"seanime/internal/extension"
"seanime/internal/library/anime"
"seanime/internal/platforms/anilist_platform"
"seanime/internal/testutil"
"seanime/internal/platforms/platform"
"seanime/internal/util"
"seanime/internal/util/limiter"
"testing"
@@ -17,24 +12,10 @@ import (
)
func TestNewMediaFetcher(t *testing.T) {
cfg := testutil.InitTestProvider(t, testutil.Anilist())
anilistClient := anilist.TestGetMockAnilistClient()
logger := util.NewLogger()
database, err := db.NewDatabase(cfg.Path.DataDir, cfg.Database.Name, logger)
if err != nil {
t.Fatal(err)
}
anilistClientRef := util.NewRef(anilistClient)
extensionBankRef := util.NewRef(extension.NewUnifiedBank())
anilistPlatform := anilist_platform.NewAnilistPlatform(anilistClientRef, extensionBankRef, logger, database)
anilistPlatform.SetUsername(cfg.Provider.AnilistUsername)
metadataProvider := metadata_provider.GetFakeProvider(t, database)
harness := newScannerFixtureHarness(t)
completeAnimeCache := anilist.NewCompleteAnimeCache()
anilistRateLimiter := limiter.NewAnilistLimiter()
dir := "E:/Anime"
tests := []struct {
name string
paths []string
@@ -78,11 +59,7 @@ func TestNewMediaFetcher(t *testing.T) {
// | Local Files |
// +---------------------+
var lfs []*anime.LocalFile
for _, path := range tt.paths {
lf := anime.NewLocalFile(path, dir)
lfs = append(lfs, lf)
}
lfs := harness.LocalFiles(tt.paths...)
// +---------------------+
// | MediaFetcher |
@@ -90,10 +67,10 @@ func TestNewMediaFetcher(t *testing.T) {
mf, err := NewMediaFetcher(t.Context(), &MediaFetcherOptions{
Enhanced: tt.enhanced,
PlatformRef: util.NewRef(anilistPlatform),
PlatformRef: util.NewRef[platform.Platform](harness.Platform),
LocalFiles: lfs,
CompleteAnimeCache: completeAnimeCache,
MetadataProviderRef: util.NewRef(metadataProvider),
MetadataProviderRef: util.NewRef(harness.MetadataProvider),
Logger: util.NewLogger(),
AnilistRateLimiter: anilistRateLimiter,
ScanLogger: scanLogger,
@@ -119,23 +96,10 @@ func TestNewMediaFetcher(t *testing.T) {
}
func TestNewEnhancedMediaFetcher(t *testing.T) {
cfg := testutil.InitTestProvider(t, testutil.Anilist())
anilistClient := anilist.TestGetMockAnilistClient()
logger := util.NewLogger()
database, err := db.NewDatabase(cfg.Path.DataDir, cfg.Database.Name, logger)
if err != nil {
t.Fatal(err)
}
anilistClientRef := util.NewRef(anilistClient)
extensionBankRef := util.NewRef(extension.NewUnifiedBank())
anilistPlatform := anilist_platform.NewAnilistPlatform(anilistClientRef, extensionBankRef, logger, database)
metaProvider := metadata_provider.GetFakeProvider(t, database)
harness := newScannerFixtureHarness(t)
completeAnimeCache := anilist.NewCompleteAnimeCache()
anilistRateLimiter := limiter.NewAnilistLimiter()
dir := "E:/Anime"
tests := []struct {
name string
paths []string
@@ -157,7 +121,7 @@ func TestNewEnhancedMediaFetcher(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
scanLogger, err := NewScanLogger("./logs")
scanLogger, err := NewScanLogger(harness.Env.RootPath("logs"))
if err != nil {
t.Fatal("expected result, got error:", err.Error())
}
@@ -166,11 +130,7 @@ func TestNewEnhancedMediaFetcher(t *testing.T) {
// | Local Files |
// +---------------------+
var lfs []*anime.LocalFile
for _, path := range tt.paths {
lf := anime.NewLocalFile(path, dir)
lfs = append(lfs, lf)
}
lfs := harness.LocalFiles(tt.paths...)
// +---------------------+
// | MediaFetcher |
@@ -178,10 +138,10 @@ func TestNewEnhancedMediaFetcher(t *testing.T) {
mf, err := NewMediaFetcher(t.Context(), &MediaFetcherOptions{
Enhanced: tt.enhanced,
PlatformRef: util.NewRef(anilistPlatform),
PlatformRef: util.NewRef[platform.Platform](harness.Platform),
LocalFiles: lfs,
CompleteAnimeCache: completeAnimeCache,
MetadataProviderRef: util.NewRef(metaProvider),
MetadataProviderRef: util.NewRef(harness.MetadataProvider),
Logger: util.NewLogger(),
AnilistRateLimiter: anilistRateLimiter,
ScanLogger: scanLogger,
@@ -206,18 +166,7 @@ func TestNewEnhancedMediaFetcher(t *testing.T) {
}
func TestFetchMediaFromLocalFiles(t *testing.T) {
cfg := testutil.InitTestProvider(t, testutil.Anilist())
anilistClient := anilist.TestGetMockAnilistClient()
logger := util.NewLogger()
database, err := db.NewDatabase(cfg.Path.DataDir, cfg.Database.Name, logger)
if err != nil {
t.Fatal(err)
}
anilistClientRef := util.NewRef(anilistClient)
extensionBankRef := util.NewRef(extension.NewUnifiedBank())
anilistPlatform := anilist_platform.NewAnilistPlatform(anilistClientRef, extensionBankRef, logger, database)
metaProvider := metadata_provider.GetFakeProvider(t, database)
harness := newScannerFixtureHarness(t)
completeAnimeCache := anilist.NewCompleteAnimeCache()
anilistRateLimiter := limiter.NewAnilistLimiter()
@@ -238,13 +187,11 @@ func TestFetchMediaFromLocalFiles(t *testing.T) {
},
}
dir := "E:/Anime"
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
scanLogger, err := NewScanLogger("./logs")
scanLogger, err := NewScanLogger(harness.Env.RootPath("logs"))
if err != nil {
t.Fatal("expected result, got error:", err.Error())
}
@@ -253,11 +200,7 @@ func TestFetchMediaFromLocalFiles(t *testing.T) {
// | Local Files |
// +---------------------+
var lfs []*anime.LocalFile
for _, path := range tt.paths {
lf := anime.NewLocalFile(path, dir)
lfs = append(lfs, lf)
}
lfs := harness.LocalFiles(tt.paths...)
// +--------------------------+
// | FetchMediaFromLocalFiles |
@@ -265,10 +208,10 @@ func TestFetchMediaFromLocalFiles(t *testing.T) {
media, ok := FetchMediaFromLocalFiles(
t.Context(),
anilistPlatform,
harness.Platform,
lfs,
completeAnimeCache,
metaProvider,
harness.MetadataProvider,
anilistRateLimiter,
scanLogger,
)

View File

@@ -3,9 +3,6 @@ package scanner
import (
"context"
"seanime/internal/api/anilist"
"seanime/internal/api/metadata_provider"
"seanime/internal/database/db"
"seanime/internal/testutil"
"seanime/internal/util"
"seanime/internal/util/limiter"
"testing"
@@ -16,18 +13,9 @@ import (
)
func TestMediaTreeAnalysis(t *testing.T) {
cfg := testutil.InitTestProvider(t, testutil.Anilist())
anilistClient := anilist.TestGetMockAnilistClient()
anilistRateLimiter := limiter.NewAnilistLimiter()
tree := anilist.NewCompleteAnimeRelationTree()
database, err := db.NewDatabase(cfg.Path.DataDir, cfg.Database.Name, util.NewLogger())
if err != nil {
t.Fatal(err)
}
metadataProvider := metadata_provider.GetFakeProvider(t, database)
harness := newScannerLiveHarness(t)
anilistClient := harness.AnilistClient
anilistRateLimiter := harness.AnilistRateLimiter
tests := []struct {
name string
@@ -64,6 +52,7 @@ func TestMediaTreeAnalysis(t *testing.T) {
t.Fatal("expected media, got not found")
}
media := mediaF.GetMedia()
tree := anilist.NewCompleteAnimeRelationTree()
// +---------------------+
// | MediaTree |
@@ -87,7 +76,7 @@ func TestMediaTreeAnalysis(t *testing.T) {
mta, err := NewMediaTreeAnalysis(&MediaTreeAnalysisOptions{
tree: tree,
metadataProviderRef: util.NewRef(metadataProvider),
metadataProviderRef: util.NewRef(harness.MetadataProvider),
rateLimiter: limiter.NewLimiter(time.Minute, 25),
})
if err != nil {
@@ -113,17 +102,9 @@ func TestMediaTreeAnalysis(t *testing.T) {
}
func TestMediaTreeAnalysis2(t *testing.T) {
cfg := testutil.InitTestProvider(t, testutil.Anilist())
anilistClient := anilist.TestGetMockAnilistClient()
anilistRateLimiter := limiter.NewAnilistLimiter()
tree := anilist.NewCompleteAnimeRelationTree()
database, err := db.NewDatabase(cfg.Path.DataDir, cfg.Database.Name, util.NewLogger())
if err != nil {
t.Fatal(err)
}
metadataProvider := metadata_provider.GetFakeProvider(t, database)
harness := newScannerLiveHarness(t)
anilistClient := harness.AnilistClient
anilistRateLimiter := harness.AnilistRateLimiter
tests := []struct {
name string
@@ -143,6 +124,7 @@ func TestMediaTreeAnalysis2(t *testing.T) {
if err != nil {
t.Fatal("expected media, got error:", err.Error())
}
tree := anilist.NewCompleteAnimeRelationTree()
// +---------------------+
// | MediaTree |
@@ -166,7 +148,7 @@ func TestMediaTreeAnalysis2(t *testing.T) {
mta, err := NewMediaTreeAnalysis(&MediaTreeAnalysisOptions{
tree: tree,
metadataProviderRef: util.NewRef(metadataProvider),
metadataProviderRef: util.NewRef(harness.MetadataProvider),
rateLimiter: limiter.NewLimiter(time.Minute, 25),
})
if err != nil {

View File

@@ -1,38 +1,22 @@
package scanner
import (
"seanime/internal/api/anilist"
"seanime/internal/api/metadata_provider"
"seanime/internal/database/db"
"seanime/internal/extension"
"seanime/internal/library/anime"
"seanime/internal/platforms/anilist_platform"
"seanime/internal/testutil"
"seanime/internal/platforms/platform"
"seanime/internal/util"
"seanime/internal/util/limiter"
"testing"
)
func TestScanLogger(t *testing.T) {
cfg := testutil.InitTestProvider(t, testutil.Anilist())
anilistClient := anilist.TestGetMockAnilistClient()
logger := util.NewLogger()
database, err := db.NewDatabase(cfg.Path.DataDir, cfg.Database.Name, logger)
if err != nil {
t.Fatal(err)
}
anilistClientRef := util.NewRef(anilistClient)
extensionBankRef := util.NewRef(extension.NewUnifiedBank())
anilistPlatform := anilist_platform.NewAnilistPlatform(anilistClientRef, extensionBankRef, logger, database)
animeCollection, err := anilistPlatform.GetAnimeCollectionWithRelations(t.Context())
harness := newScannerFixtureHarness(t)
logger := harness.Logger
animeCollection, err := harness.Platform.GetAnimeCollectionWithRelations(t.Context())
if err != nil {
t.Fatal(err.Error())
}
if animeCollection == nil {
t.Fatal("expected anime collection, got nil")
}
allMedia := animeCollection.GetAllAnime()
metadataProvider := metadata_provider.GetFakeProvider(t, database)
completeAnimeCache := anilist.NewCompleteAnimeCache()
anilistRateLimiter := limiter.NewAnilistLimiter()
tests := []struct {
name string
@@ -55,7 +39,7 @@ func TestScanLogger(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
scanLogger, err := NewScanLogger("./logs")
scanLogger, err := NewScanLogger(harness.Env.RootPath("logs"))
if err != nil {
t.Fatal("expected result, got error:", err.Error())
}
@@ -64,11 +48,7 @@ func TestScanLogger(t *testing.T) {
// | Local Files |
// +---------------------+
var lfs []*anime.LocalFile
for _, path := range tt.paths {
lf := anime.NewLocalFile(path, "E:/Anime")
lfs = append(lfs, lf)
}
lfs := harness.LocalFiles(tt.paths...)
// +---------------------+
// | MediaContainer |
@@ -107,10 +87,10 @@ func TestScanLogger(t *testing.T) {
fh := FileHydrator{
LocalFiles: lfs,
AllMedia: mc.NormalizedMedia,
CompleteAnimeCache: completeAnimeCache,
PlatformRef: util.NewRef(anilistPlatform),
MetadataProviderRef: util.NewRef(metadataProvider),
AnilistRateLimiter: anilistRateLimiter,
CompleteAnimeCache: harness.CompleteAnimeCache,
PlatformRef: util.NewRef[platform.Platform](harness.Platform),
MetadataProviderRef: util.NewRef(harness.MetadataProvider),
AnilistRateLimiter: harness.AnilistRateLimiter,
Logger: logger,
ScanLogger: scanLogger,
ScanSummaryLogger: nil,

View File

@@ -3,14 +3,9 @@ package scanner
import (
"os"
"path/filepath"
"seanime/internal/api/anilist"
"seanime/internal/api/metadata_provider"
"seanime/internal/database/db"
"seanime/internal/events"
"seanime/internal/extension"
"seanime/internal/library/anime"
"seanime/internal/platforms/anilist_platform"
"seanime/internal/testutil"
"seanime/internal/platforms/platform"
"seanime/internal/util"
"testing"
@@ -18,14 +13,8 @@ import (
)
func TestScanner_Shelving(t *testing.T) {
cfg := testutil.InitTestProvider(t, testutil.Anilist())
anilistClient := anilist.TestGetMockAnilistClient()
logger := util.NewLogger()
database, err := db.NewDatabase(cfg.Path.DataDir, cfg.Database.Name, logger)
if err != nil {
t.Fatal(err)
}
harness := newScannerFixtureHarness(t)
logger := harness.Logger
// a temporary directory for the library
tempDir, err := os.MkdirTemp("", "seanime_test_library")
@@ -42,11 +31,6 @@ func TestScanner_Shelving(t *testing.T) {
filename := "[SubsPlease] 86 - Eighty Six - 20v2 (1080p) [30072859].mkv"
filePath := filepath.Join(tempDir, filename)
anilistClientRef := util.NewRef[anilist.AnilistClient](anilistClient)
extensionBankRef := util.NewRef(extension.NewUnifiedBank())
anilistPlatform := anilist_platform.NewAnilistPlatform(anilistClientRef, extensionBankRef, logger, database)
anilistPlatform.SetUsername(cfg.Provider.AnilistUsername)
metadataProvider := metadata_provider.GetFakeProvider(t, database)
wsEventManager := events.NewMockWSEventManager(util.NewLogger())
t.Run("Shelve missing locked file", func(t *testing.T) {
@@ -60,8 +44,8 @@ func TestScanner_Shelving(t *testing.T) {
scanner := &Scanner{
DirPath: tempDir,
Enhanced: false,
PlatformRef: util.NewRef(anilistPlatform),
MetadataProviderRef: util.NewRef(metadataProvider),
PlatformRef: util.NewRef[platform.Platform](harness.Platform),
MetadataProviderRef: util.NewRef(harness.MetadataProvider),
Logger: logger,
WSEventManager: wsEventManager,
ExistingLocalFiles: existingLfs,
@@ -105,8 +89,8 @@ func TestScanner_Shelving(t *testing.T) {
scanner := &Scanner{
DirPath: tempDir,
Enhanced: false,
PlatformRef: util.NewRef(anilistPlatform),
MetadataProviderRef: util.NewRef(metadataProvider),
PlatformRef: util.NewRef[platform.Platform](harness.Platform),
MetadataProviderRef: util.NewRef(harness.MetadataProvider),
Logger: logger,
WSEventManager: wsEventManager,
ExistingLocalFiles: existingLfs,
@@ -147,8 +131,8 @@ func TestScanner_Shelving(t *testing.T) {
scanner := &Scanner{
DirPath: tempDir,
Enhanced: false,
PlatformRef: util.NewRef(anilistPlatform),
MetadataProviderRef: util.NewRef(metadataProvider),
PlatformRef: util.NewRef[platform.Platform](harness.Platform),
MetadataProviderRef: util.NewRef(harness.MetadataProvider),
Logger: logger,
WSEventManager: wsEventManager,
ExistingLocalFiles: existingLfs,
@@ -190,8 +174,8 @@ func TestScanner_Shelving(t *testing.T) {
DirPath: tempDir, // The main scan dir is tempDir
OtherDirPaths: []string{missingDir}, // We include the missing dir as a library path
Enhanced: false,
PlatformRef: util.NewRef(anilistPlatform),
MetadataProviderRef: util.NewRef(metadataProvider),
PlatformRef: util.NewRef[platform.Platform](harness.Platform),
MetadataProviderRef: util.NewRef(harness.MetadataProvider),
Logger: logger,
WSEventManager: wsEventManager,
ExistingLocalFiles: existingLfs,

View File

@@ -1,14 +1,9 @@
package scanner
import (
"seanime/internal/api/anilist"
"seanime/internal/api/metadata_provider"
"seanime/internal/database/db"
"seanime/internal/events"
"seanime/internal/extension"
"seanime/internal/library/anime"
"seanime/internal/platforms/anilist_platform"
"seanime/internal/testutil"
"seanime/internal/platforms/platform"
"seanime/internal/util"
"testing"
)
@@ -16,20 +11,9 @@ import (
//----------------------------------------------------------------------------------------------------------------------
func TestScanner_Scan(t *testing.T) {
cfg := testutil.InitTestProvider(t, testutil.Anilist())
anilistClient := anilist.TestGetMockAnilistClient()
logger := util.NewLogger()
database, err := db.NewDatabase(cfg.Path.DataDir, cfg.Database.Name, logger)
if err != nil {
t.Fatal(err)
}
anilistClientRef := util.NewRef(anilistClient)
extensionBankRef := util.NewRef(extension.NewUnifiedBank())
anilistPlatform := anilist_platform.NewAnilistPlatform(anilistClientRef, extensionBankRef, logger, database)
metadataProvider := metadata_provider.GetFakeProvider(t, database)
harness := newScannerFixtureHarness(t)
wsEventManager := events.NewMockWSEventManager(util.NewLogger())
dir := "E:/Anime"
dir := harness.LibraryDir
tests := []struct {
name string
@@ -63,8 +47,8 @@ func TestScanner_Scan(t *testing.T) {
scanner := &Scanner{
DirPath: dir,
Enhanced: false,
PlatformRef: util.NewRef(anilistPlatform),
MetadataProviderRef: util.NewRef(metadataProvider),
PlatformRef: util.NewRef[platform.Platform](harness.Platform),
MetadataProviderRef: util.NewRef(harness.MetadataProvider),
Logger: util.NewLogger(),
WSEventManager: wsEventManager,
ExistingLocalFiles: existingLfs,

View File

@@ -0,0 +1,85 @@
package scanner
import (
"seanime/internal/api/anilist"
"seanime/internal/api/metadata_provider"
"seanime/internal/database/db"
"seanime/internal/events"
"seanime/internal/extension"
"seanime/internal/library/anime"
"seanime/internal/platforms/anilist_platform"
"seanime/internal/testutil"
"seanime/internal/util"
"seanime/internal/util/limiter"
"testing"
"github.com/rs/zerolog"
)
const scannerTestLibraryDir = "E:/Anime"
type scannerTestHarness struct {
Env *testutil.TestEnv
Config *testutil.Config
Logger *zerolog.Logger
Database *db.Database
AnilistClient anilist.AnilistClient
Platform *anilist_platform.AnilistPlatform
MetadataProvider metadata_provider.Provider
CompleteAnimeCache *anilist.CompleteAnimeCache
AnilistRateLimiter *limiter.Limiter
WSEventManager events.WSEventManagerInterface
LibraryDir string
}
func newScannerFixtureHarness(t testing.TB) *scannerTestHarness {
t.Helper()
env := testutil.NewTestEnv(t)
return newScannerHarness(t, env, anilist.NewTestAnilistClient(), "")
}
func newScannerLiveHarness(t testing.TB) *scannerTestHarness {
t.Helper()
env := testutil.NewTestEnv(t, testutil.Anilist())
cfg := env.Config()
return newScannerHarness(t, env, anilist.NewAnilistClient(cfg.Provider.AnilistJwt, ""), cfg.Provider.AnilistUsername)
}
func newScannerHarness(t testing.TB, env *testutil.TestEnv, client anilist.AnilistClient, username string) *scannerTestHarness {
t.Helper()
logger := env.Logger()
database := env.MustNewDatabase(logger)
anilistClientRef := util.NewRef(client)
extensionBankRef := util.NewRef(extension.NewUnifiedBank())
platform := anilist_platform.NewAnilistPlatform(anilistClientRef, extensionBankRef, logger, database).(*anilist_platform.AnilistPlatform)
if username != "" {
platform.SetUsername(username)
}
return &scannerTestHarness{
Env: env,
Config: env.Config(),
Logger: logger,
Database: database,
AnilistClient: client,
Platform: platform,
MetadataProvider: metadata_provider.NewTestProviderWithEnv(env, database),
CompleteAnimeCache: anilist.NewCompleteAnimeCache(),
AnilistRateLimiter: limiter.NewAnilistLimiter(),
WSEventManager: events.NewMockWSEventManager(logger),
LibraryDir: scannerTestLibraryDir,
}
}
func (h *scannerTestHarness) LocalFiles(paths ...string) []*anime.LocalFile {
localFiles := make([]*anime.LocalFile, 0, len(paths))
for _, path := range paths {
localFiles = append(localFiles, anime.NewLocalFile(path, h.LibraryDir))
}
return localFiles
}

View File

@@ -369,7 +369,7 @@ func (m *ManagerImpl) TrackAnime(mId int) error {
err := m.localDb.gormdb.Create(s).Error
if err != nil {
m.logger.Error().Msgf("local manager: Failed to add anime %d to local database: %w", mId, err)
m.logger.Error().Err(err).Msgf("local manager: Failed to add anime %d to local database", mId)
return fmt.Errorf("failed to add anime %d to local database: %w", mId, err)
}
@@ -426,7 +426,7 @@ func (m *ManagerImpl) TrackManga(mId int) error {
err := m.localDb.gormdb.Create(s).Error
if err != nil {
m.logger.Error().Msgf("local manager: Failed to add manga %d to local database: %w", mId, err)
m.logger.Error().Err(err).Msgf("local manager: Failed to add manga %d to local database", mId)
return fmt.Errorf("failed to add manga %d to local database: %w", mId, err)
}

View File

@@ -684,7 +684,6 @@ func (q *Syncer) synchronizeAnime(diff *AnimeDiffResult) {
}
// The snapshot is up-to-date
return
}
// synchronizeManga creates or updates the manga snapshot in the local database.
@@ -775,5 +774,4 @@ func (q *Syncer) synchronizeManga(diff *MangaDiffResult) {
}
// The snapshot is up-to-date
return
}

View File

@@ -4,7 +4,6 @@ import (
"errors"
"fmt"
"seanime/internal/api/anilist"
"seanime/internal/database/db"
"seanime/internal/extension"
"seanime/internal/platforms/anilist_platform"
"seanime/internal/testutil"
@@ -16,22 +15,19 @@ import (
)
func testSetupManager(t *testing.T) (Manager, *anilist.AnimeCollection, *anilist.MangaCollection) {
cfg := testutil.InitTestProvider(t, testutil.Anilist())
env := testutil.NewTestEnv(t)
logger := env.Logger()
logger := util.NewLogger()
database, err := db.NewDatabase(cfg.Path.DataDir, cfg.Database.Name, logger)
require.NoError(t, err)
anilistClient := anilist.NewAnilistClient(cfg.Provider.AnilistJwt, "")
database := env.MustNewDatabase(logger)
anilistClient := anilist.NewTestAnilistClient()
extensionBankRef := util.NewRef(extension.NewUnifiedBank())
anilistPlatform := anilist_platform.NewAnilistPlatform(util.NewRef[anilist.AnilistClient](anilistClient), extensionBankRef, logger, database)
anilistPlatform.SetUsername(cfg.Provider.AnilistUsername)
animeCollection, err := anilistPlatform.GetAnimeCollection(t.Context(), true)
require.NoError(t, err)
mangaCollection, err := anilistPlatform.GetMangaCollection(t.Context(), true)
require.NoError(t, err)
manager := GetMockManager(t, database)
manager := NewTestManager(t, database)
manager.SetAnimeCollection(animeCollection)
manager.SetMangaCollection(mangaCollection)
@@ -72,7 +68,7 @@ func TestSync2(t *testing.T) {
break
}
anilist.TestModifyAnimeCollectionEntry(animeCollection, 130003, anilist.TestModifyAnimeCollectionEntryInput{
anilist.PatchAnimeCollectionEntry(animeCollection, 130003, anilist.AnimeCollectionEntryPatch{
Status: new(anilist.MediaListStatusCompleted),
Progress: new(12), // Mock progress
})

View File

@@ -1,12 +1,14 @@
package local
import (
"path/filepath"
"seanime/internal/api/anilist"
"seanime/internal/api/metadata_provider"
"seanime/internal/database/db"
"seanime/internal/database/db_bridge"
"seanime/internal/database/models"
"seanime/internal/events"
"seanime/internal/extension"
"seanime/internal/library/anime"
"seanime/internal/manga"
"seanime/internal/platforms/anilist_platform"
"seanime/internal/platforms/platform"
@@ -17,28 +19,36 @@ import (
"github.com/stretchr/testify/require"
)
func GetMockManager(t *testing.T, db *db.Database) Manager {
cfg := testutil.LoadConfig(t)
func NewTestManager(t *testing.T, db *db.Database) Manager {
env := testutil.NewTestEnv(t)
logger := util.NewLogger()
metadataProvider := metadata_provider.GetFakeProvider(t, db)
metadataProviderRef := util.NewRef[metadata_provider.Provider](metadataProvider)
mangaRepository := manga.GetFakeRepository(t, db)
logger := env.Logger()
metadataProvider := metadata_provider.NewTestProviderWithEnv(env, db)
metadataProviderRef := util.NewRef(metadataProvider)
mangaRepository := manga.NewTestRepositoryWithEnv(env, db)
wsEventManager := events.NewMockWSEventManager(logger)
anilistClient := anilist.NewMockAnilistClient()
anilistClient := anilist.NewFixtureAnilistClient()
anilistClientRef := util.NewRef[anilist.AnilistClient](anilistClient)
extensionBankRef := util.NewRef(extension.NewUnifiedBank())
anilistPlatform := anilist_platform.NewAnilistPlatform(anilistClientRef, extensionBankRef, logger, db)
anilistPlatformRef := util.NewRef[platform.Platform](anilistPlatform)
localDir := filepath.Join(cfg.Path.DataDir, "offline")
assetsDir := filepath.Join(cfg.Path.DataDir, "offline", "assets")
localDir := env.MustMkdirData("offline")
assetsDir := env.MustMkdirData("offline", "assets")
var localFilesCount int64
err := db.Gorm().Model(&models.LocalFiles{}).Count(&localFilesCount).Error
require.NoError(t, err)
if localFilesCount == 0 {
_, err = db_bridge.InsertLocalFiles(db, make([]*anime.LocalFile, 0))
require.NoError(t, err)
}
m, err := NewManager(&NewManagerOptions{
LocalDir: localDir,
AssetDir: assetsDir,
Logger: util.NewLogger(),
Logger: logger,
MetadataProviderRef: metadataProviderRef,
MangaRepository: mangaRepository,
Database: db,

View File

@@ -1,29 +1,32 @@
package chapter_downloader
import (
"bytes"
"image"
"image/color"
"image/png"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"testing"
"seanime/internal/database/db"
"seanime/internal/events"
hibikemanga "seanime/internal/extension/hibike/manga"
"seanime/internal/testutil"
"seanime/internal/util"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/goccy/go-json"
"github.com/stretchr/testify/require"
)
func TestQueue(t *testing.T) {
cfg := testutil.InitTestProvider(t)
func newTestDownloader(t *testing.T) (*Downloader, *db.Database, string) {
t.Helper()
tempDir := t.TempDir()
logger := util.NewLogger()
database, err := db.NewDatabase(tempDir, cfg.Database.Name, logger)
if err != nil {
t.Fatalf("Failed to create database: %v", err)
}
downloadDir := t.TempDir()
env := testutil.NewTestEnv(t)
logger := env.Logger()
database := env.MustNewDatabase(logger)
downloadDir := env.MustMkdir("downloads")
downloader := NewDownloader(&NewDownloaderOptions{
Logger: logger,
@@ -32,81 +35,107 @@ func TestQueue(t *testing.T) {
DownloadDir: downloadDir,
})
downloader.Start()
tests := []struct {
name string
providerName string
provider hibikemanga.Provider
mangaId string
mediaId int
chapterIndex uint
}{
{
//providerName: manga_providers.REPLACE,
//provider: manga_providers.REPLACE(util.NewLogger()),
//name: "Jujutsu Kaisen",
//mangaId: "TA22I5O7",
//chapterIndex: 258,
//mediaId: 101517,
},
{
//providerName: manga_providers.REPLACE,
//provider: manga_providers.REPLACE(util.NewLogger()),
//name: "Jujutsu Kaisen",
//mangaId: "TA22I5O7",
//chapterIndex: 259,
//mediaId: 101517,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// SETUP
chapters, err := tt.provider.FindChapters(tt.mangaId)
if assert.NoError(t, err, "comick.FindChapters() error") {
assert.NotEmpty(t, chapters, "chapters is empty")
var chapterInfo *hibikemanga.ChapterDetails
for _, chapter := range chapters {
if chapter.Index == tt.chapterIndex {
chapterInfo = chapter
break
}
}
if assert.NotNil(t, chapterInfo, "chapter not found") {
pages, err := tt.provider.FindChapterPages(chapterInfo.ID)
if assert.NoError(t, err, "provider.FindChapterPages() error") {
assert.NotEmpty(t, pages, "pages is empty")
//
// TEST
//
err := downloader.AddToQueue(DownloadOptions{
DownloadID: DownloadID{
Provider: string(tt.providerName),
MediaId: tt.mediaId,
ChapterId: chapterInfo.ID,
ChapterNumber: chapterInfo.Chapter,
},
Pages: pages,
StartNow: true,
})
if err != nil {
t.Fatalf("Failed to download chapter: %v", err)
}
}
}
}
})
}
time.Sleep(10 * time.Second)
return downloader, database, downloadDir
}
func newTestPNG(t *testing.T) []byte {
t.Helper()
var buf bytes.Buffer
img := image.NewRGBA(image.Rect(0, 0, 1, 1))
img.Set(0, 0, color.RGBA{R: 255, G: 128, B: 64, A: 255})
require.NoError(t, png.Encode(&buf, img))
return buf.Bytes()
}
func TestQueueAddsItemToDatabase(t *testing.T) {
downloader, database, _ := newTestDownloader(t)
pages := []*hibikemanga.ChapterPage{{
Index: 0,
URL: "https://example.com/01.png",
}}
id := DownloadID{
Provider: "test-provider",
MediaId: 101517,
ChapterId: "chapter-1",
ChapterNumber: "1",
}
err := downloader.AddToQueue(DownloadOptions{
DownloadID: id,
Pages: pages,
StartNow: false,
})
require.NoError(t, err)
next, err := database.GetNextChapterDownloadQueueItem()
require.NoError(t, err)
require.NotNil(t, next)
require.Equal(t, id.Provider, next.Provider)
require.Equal(t, id.MediaId, next.MediaID)
require.Equal(t, id.ChapterId, next.ChapterID)
require.Equal(t, id.ChapterNumber, next.ChapterNumber)
require.Equal(t, string(QueueStatusNotStarted), next.Status)
require.NotEmpty(t, next.PageData)
}
func TestDownloadChapterImagesWritesRegistry(t *testing.T) {
downloader, database, downloadDir := newTestDownloader(t)
imageData := newTestPNG(t)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "image/png")
_, _ = w.Write(imageData)
}))
defer server.Close()
pages := []*hibikemanga.ChapterPage{{
Index: 0,
URL: server.URL + "/page.png",
}}
id := DownloadID{
Provider: "test-provider",
MediaId: 101517,
ChapterId: "chapter-1",
ChapterNumber: "1",
}
err := downloader.AddToQueue(DownloadOptions{
DownloadID: id,
Pages: pages,
StartNow: false,
})
require.NoError(t, err)
require.NoError(t, database.UpdateChapterDownloadQueueItemStatus(id.Provider, id.MediaId, id.ChapterId, string(QueueStatusDownloading)))
downloader.queue.current = &QueueInfo{
DownloadID: id,
Pages: pages,
Status: QueueStatusDownloading,
}
err = downloader.downloadChapterImages(downloader.queue.current)
require.NoError(t, err)
chapterDir := filepath.Join(downloadDir, FormatChapterDirName(id.Provider, id.MediaId, id.ChapterId, id.ChapterNumber))
registryPath := filepath.Join(chapterDir, "registry.json")
registryBytes, err := os.ReadFile(registryPath)
require.NoError(t, err)
var registry Registry
require.NoError(t, json.Unmarshal(registryBytes, &registry))
require.Len(t, registry, 1)
pageInfo, ok := registry[0]
require.True(t, ok)
require.Equal(t, "01.png", pageInfo.Filename)
require.Equal(t, server.URL+"/page.png", pageInfo.OriginalURL)
_, err = os.Stat(filepath.Join(chapterDir, pageInfo.Filename))
require.NoError(t, err)
queueItems, err := database.GetChapterDownloadQueue()
require.NoError(t, err)
require.Empty(t, queueItems)
}

View File

@@ -1,25 +1,24 @@
package manga
import (
"path/filepath"
"seanime/internal/database/db"
"seanime/internal/events"
"seanime/internal/extension"
"seanime/internal/testutil"
"seanime/internal/util"
"seanime/internal/util/filecache"
"testing"
)
func GetFakeRepository(t *testing.T, db *db.Database) *Repository {
cfg := testutil.LoadConfig(t)
func NewTestRepository(t *testing.T, db *db.Database) *Repository {
t.Helper()
logger := util.NewLogger()
cacheDir := filepath.Join(cfg.Path.DataDir, "cache")
fileCacher, err := filecache.NewCacher(cacheDir)
if err != nil {
t.Fatal(err)
}
return NewTestRepositoryWithEnv(testutil.NewTestEnv(t), db)
}
func NewTestRepositoryWithEnv(env *testutil.TestEnv, db *db.Database) *Repository {
logger := env.Logger()
cacheDir := env.EnsureCacheDir()
fileCacher := env.NewCacher()
repository := NewRepository(&NewRepositoryOptions{
Logger: logger,
@@ -27,7 +26,7 @@ func GetFakeRepository(t *testing.T, db *db.Database) *Repository {
CacheDir: cacheDir,
ServerURI: "",
WsEventManager: events.NewMockWSEventManager(logger),
DownloadDir: filepath.Join(cfg.Path.DataDir, "manga"),
DownloadDir: env.MustMkdirData("manga"),
Database: db,
ExtensionBankRef: util.NewRef(extension.NewUnifiedBank()),
})

View File

@@ -27,6 +27,7 @@ func TestMpcHc_Start(t *testing.T) {
func TestMpcHc_Play(t *testing.T) {
cfg := testutil.InitTestProvider(t, testutil.MediaPlayer())
sampleVideoPath := testutil.RequireSampleVideoPath(t)
mpc := &MpcHc{
Host: cfg.Provider.MpcHost,
@@ -38,7 +39,7 @@ func TestMpcHc_Play(t *testing.T) {
err := mpc.Start()
assert.NoError(t, err)
res, err := mpc.OpenAndPlay("E:\\ANIME\\Violet.Evergarden.The.Movie.1080p.Dual.Audio.BDRip.10.bits.DD.x265-EMBER.mkv")
res, err := mpc.OpenAndPlay(sampleVideoPath)
assert.NoError(t, err)
t.Log(res)
@@ -69,6 +70,7 @@ func TestMpcHc_GetVariables(t *testing.T) {
func TestMpcHc_Seek(t *testing.T) {
cfg := testutil.InitTestProvider(t, testutil.MediaPlayer())
sampleVideoPath := testutil.RequireSampleVideoPath(t)
mpc := &MpcHc{
Host: cfg.Provider.MpcHost,
@@ -80,7 +82,7 @@ func TestMpcHc_Seek(t *testing.T) {
err := mpc.Start()
assert.NoError(t, err)
_, err = mpc.OpenAndPlay("E:\\ANIME\\[SubsPlease] Bocchi the Rock! (01-12) (1080p) [Batch]\\[SubsPlease] Bocchi the Rock! - 01v2 (1080p) [ABDDAE16].mkv")
_, err = mpc.OpenAndPlay(sampleVideoPath)
assert.NoError(t, err)
err = mpc.Pause()

View File

@@ -24,6 +24,7 @@ type (
logger *zerolog.Logger
username mo.Option[string]
anilistClient anilist.AnilistClient
useFixtureCollections bool
animeCollection mo.Option[*anilist.AnimeCollection]
rawAnimeCollection mo.Option[*anilist.AnimeCollection]
mangaCollection mo.Option[*anilist.MangaCollection]
@@ -37,17 +38,20 @@ type (
)
func NewAnilistPlatform(anilistClientRef *util.Ref[anilist.AnilistClient], extensionBankRef *util.Ref[*extension.UnifiedBank], logger *zerolog.Logger, db *db.Database, logoutFunc ...func()) platform.Platform {
_, useFixtureCollections := anilistClientRef.Get().(*anilist.FixtureAnilistClient)
ap := &AnilistPlatform{
anilistClient: shared_platform.NewCacheLayer(anilistClientRef, logoutFunc...),
logger: logger,
username: mo.None[string](),
animeCollection: mo.None[*anilist.AnimeCollection](),
rawAnimeCollection: mo.None[*anilist.AnimeCollection](),
mangaCollection: mo.None[*anilist.MangaCollection](),
rawMangaCollection: mo.None[*anilist.MangaCollection](),
extensionBankRef: extensionBankRef,
helper: shared_platform.NewPlatformHelper(extensionBankRef, db, logger),
db: db,
anilistClient: shared_platform.NewCacheLayer(anilistClientRef, logoutFunc...),
logger: logger,
username: mo.None[string](),
useFixtureCollections: useFixtureCollections,
animeCollection: mo.None[*anilist.AnimeCollection](),
rawAnimeCollection: mo.None[*anilist.AnimeCollection](),
mangaCollection: mo.None[*anilist.MangaCollection](),
rawMangaCollection: mo.None[*anilist.MangaCollection](),
extensionBankRef: extensionBankRef,
helper: shared_platform.NewPlatformHelper(extensionBankRef, db, logger),
db: db,
}
return ap
@@ -76,6 +80,17 @@ func (ap *AnilistPlatform) SetAnilistClient(client anilist.AnilistClient) {
ap.anilistClient = client
}
func (ap *AnilistPlatform) getUsername() (*string, bool) {
if ap.username.IsPresent() {
return ap.username.ToPointer(), true
}
if ap.useFixtureCollections {
return nil, true
}
return nil, false
}
func (ap *AnilistPlatform) UpdateEntry(ctx context.Context, mediaID int, status *anilist.MediaListStatus, scoreRaw *int, progress *int, startedAt *anilist.FuzzyDateInput, completedAt *anilist.FuzzyDateInput) error {
ap.logger.Trace().Msg("anilist platform: Updating entry")
@@ -337,7 +352,7 @@ func (ap *AnilistPlatform) GetAnimeCollection(ctx context.Context, bypassCache b
return event.AnimeCollection, nil
}
if ap.username.IsAbsent() {
if _, ok := ap.getUsername(); !ok {
return nil, nil
}
@@ -368,7 +383,7 @@ func (ap *AnilistPlatform) GetRawAnimeCollection(ctx context.Context, bypassCach
return event.AnimeCollection, nil
}
if ap.username.IsAbsent() {
if _, ok := ap.getUsername(); !ok {
return nil, nil
}
@@ -389,7 +404,7 @@ func (ap *AnilistPlatform) GetRawAnimeCollection(ctx context.Context, bypassCach
}
func (ap *AnilistPlatform) RefreshAnimeCollection(ctx context.Context) (*anilist.AnimeCollection, error) {
if ap.username.IsAbsent() {
if _, ok := ap.getUsername(); !ok {
return nil, nil
}
@@ -418,12 +433,13 @@ func (ap *AnilistPlatform) RefreshAnimeCollection(ctx context.Context) (*anilist
}
func (ap *AnilistPlatform) refreshAnimeCollection(ctx context.Context) error {
if ap.username.IsAbsent() {
userName, ok := ap.getUsername()
if !ok {
return errors.New("anilist: Username is not set")
}
// Else, get the collection from Anilist
collection, err := ap.anilistClient.AnimeCollection(ctx, ap.username.ToPointer())
collection, err := ap.anilistClient.AnimeCollection(ctx, userName)
if err != nil {
return err
}
@@ -450,11 +466,12 @@ func (ap *AnilistPlatform) refreshAnimeCollection(ctx context.Context) error {
func (ap *AnilistPlatform) GetAnimeCollectionWithRelations(ctx context.Context) (*anilist.AnimeCollectionWithRelations, error) {
ap.logger.Trace().Msg("anilist platform: Fetching anime collection with relations")
if ap.username.IsAbsent() {
userName, ok := ap.getUsername()
if !ok {
return nil, nil
}
ret, err := ap.anilistClient.AnimeCollectionWithRelations(ctx, ap.username.ToPointer())
ret, err := ap.anilistClient.AnimeCollectionWithRelations(ctx, userName)
if err != nil {
return nil, err
}
@@ -474,7 +491,7 @@ func (ap *AnilistPlatform) GetMangaCollection(ctx context.Context, bypassCache b
return event.MangaCollection, nil
}
if ap.username.IsAbsent() {
if _, ok := ap.getUsername(); !ok {
return nil, nil
}
@@ -508,7 +525,7 @@ func (ap *AnilistPlatform) GetRawMangaCollection(ctx context.Context, bypassCach
return event.MangaCollection, nil
}
if ap.username.IsAbsent() {
if _, ok := ap.getUsername(); !ok {
return nil, nil
}
@@ -529,7 +546,7 @@ func (ap *AnilistPlatform) GetRawMangaCollection(ctx context.Context, bypassCach
}
func (ap *AnilistPlatform) RefreshMangaCollection(ctx context.Context) (*anilist.MangaCollection, error) {
if ap.username.IsAbsent() {
if _, ok := ap.getUsername(); !ok {
return nil, nil
}
@@ -558,11 +575,12 @@ func (ap *AnilistPlatform) RefreshMangaCollection(ctx context.Context) (*anilist
}
func (ap *AnilistPlatform) refreshMangaCollection(ctx context.Context) error {
if ap.username.IsAbsent() {
userName, ok := ap.getUsername()
if !ok {
return errors.New("anilist: Username is not set")
}
collection, err := ap.anilistClient.MangaCollection(ctx, ap.username.ToPointer())
collection, err := ap.anilistClient.MangaCollection(ctx, userName)
if err != nil {
return err
}

View File

@@ -4,6 +4,7 @@ import (
"os"
"path/filepath"
"runtime"
"strconv"
"sync"
"testing"
@@ -57,13 +58,22 @@ type ProviderConfig struct {
}
type PathConfig struct {
DataDir string `mapstructure:"dataDir"`
DataDir string `mapstructure:"dataDir"`
SampleVideoPath string `mapstructure:"sampleVideoPath"`
}
type DatabaseConfig struct {
Name string `mapstructure:"name"`
}
func defaultConfig() *Config {
return &Config{
Path: PathConfig{},
Database: DatabaseConfig{Name: defaultTestDatabaseName},
Flags: FlagsConfig{},
}
}
// SkipFunc conditionally skips a test based on configuration.
type SkipFunc func(t testing.TB, cfg *Config)
@@ -115,12 +125,20 @@ func TestDataPath(name string) string {
return filepath.Join(ProjectRoot(), "test", "testdata", name+".json")
}
const (
SampleVideoPathEnv = "TEST_SAMPLE_VIDEO_PATH"
RecordAnilistFixturesEnvName = "SEANIME_TEST_RECORD_ANILIST_FIXTURES"
)
// InitTestProvider loads the test configuration and skips the test
// if any of the provided skip functions decide it should not run.
func InitTestProvider(t testing.TB, flags ...SkipFunc) *Config {
t.Helper()
cfg := LoadConfig(t)
cfg, err := readConfig()
if err != nil {
cfg = defaultConfig()
}
for _, fn := range flags {
fn(t, cfg)
@@ -166,6 +184,47 @@ func readConfig() (*Config, error) {
return &c, nil
}
func cloneConfig(cfg *Config) *Config {
if cfg == nil {
return nil
}
return new(*cfg)
}
// RequireSampleVideoPath returns the configured sample media path for external
// playback tests. It respects TEST_SAMPLE_VIDEO_PATH and skips if unset.
func RequireSampleVideoPath(t testing.TB) string {
t.Helper()
if path := os.Getenv(SampleVideoPathEnv); path != "" {
return path
}
cfg := LoadConfig(t)
if cfg.Path.SampleVideoPath == "" {
t.Skip("media playback tests require path.sampleVideoPath or TEST_SAMPLE_VIDEO_PATH")
}
return cfg.Path.SampleVideoPath
}
// ShouldRecordAnilistFixtures reports whether AniList mock helpers may write
// back into repository fixtures during a test run.
func ShouldRecordAnilistFixtures() bool {
value := os.Getenv(RecordAnilistFixturesEnvName)
if value == "" {
return false
}
enabled, err := strconv.ParseBool(value)
if err != nil {
return false
}
return enabled
}
func Anilist() SkipFunc {
return func(t testing.TB, cfg *Config) {
t.Helper()

View File

@@ -21,3 +21,14 @@ func TestLoadConfig_IsolatedInstances(t *testing.T) {
first.Path.DataDir = t.TempDir()
assert.NotEqual(t, first.Path.DataDir, second.Path.DataDir)
}
func TestInitTestProvider_DefaultsWithoutConfig(t *testing.T) {
t.Setenv("TEST_CONFIG_PATH", t.TempDir())
cfg := InitTestProvider(t)
assert.NotNil(t, cfg)
assert.Equal(t, defaultTestDatabaseName, cfg.Database.Name)
assert.Empty(t, cfg.Path.DataDir)
assert.False(t, cfg.Flags.EnableAnilistTests)
}

217
internal/testutil/env.go Normal file
View File

@@ -0,0 +1,217 @@
package testutil
import (
"os"
pathpkg "path"
"path/filepath"
"seanime/internal/database/db"
"seanime/internal/util"
"seanime/internal/util/filecache"
"strings"
"testing"
"unicode"
"github.com/rs/zerolog"
)
const defaultTestDatabaseName = "seanime-test"
type TestEnv struct {
t testing.TB
RootDir string
DataDir string
CacheDir string
logger *zerolog.Logger
cfg *Config
}
func NewTestEnv(t testing.TB, flags ...SkipFunc) *TestEnv {
t.Helper()
cfg, err := readConfig()
if err != nil {
if len(flags) > 0 {
t.Skipf("testutil: skipping flagged test env because test config is unavailable: %v", err)
}
cfg = defaultConfig()
}
for _, fn := range flags {
fn(t, cfg)
}
rootDir := t.TempDir()
dataDir := filepath.Join(rootDir, "data")
env := &TestEnv{
t: t,
RootDir: rootDir,
DataDir: dataDir,
CacheDir: filepath.Join(dataDir, "cache"),
logger: util.NewLogger(),
cfg: cloneConfig(cfg),
}
env.cfg.Path.DataDir = env.DataDir
env.cfg.Database.Name = sanitizeTestName(t.Name())
env.mustMkdirAll(env.DataDir)
env.mustMkdirAll(env.CacheDir)
return env
}
func (env *TestEnv) Logger() *zerolog.Logger {
return env.logger
}
func (env *TestEnv) Config() *Config {
return cloneConfig(env.cfg)
}
func (env *TestEnv) RootPath(parts ...string) string {
return filepath.Join(append([]string{env.RootDir}, parts...)...)
}
func (env *TestEnv) DataPath(parts ...string) string {
return filepath.Join(append([]string{env.DataDir}, parts...)...)
}
func (env *TestEnv) CachePath(parts ...string) string {
return filepath.Join(append([]string{env.CacheDir}, parts...)...)
}
func (env *TestEnv) EnsureDir(parts ...string) string {
return env.mustMkdirAll(env.RootPath(parts...))
}
func (env *TestEnv) EnsureDataDir(parts ...string) string {
return env.mustMkdirAll(env.DataPath(parts...))
}
func (env *TestEnv) EnsureCacheDir(parts ...string) string {
return env.mustMkdirAll(env.CachePath(parts...))
}
func (env *TestEnv) MustMkdir(parts ...string) string {
return env.EnsureDir(parts...)
}
func (env *TestEnv) MustMkdirData(parts ...string) string {
return env.EnsureDataDir(parts...)
}
func (env *TestEnv) FixturePath(path string) string {
return filepath.Join(env.RootDir, FixtureRelPath(path))
}
func (env *TestEnv) MustWriteFixtureFile(path string, data []byte) string {
env.t.Helper()
target := env.FixturePath(path)
env.mustMkdirAll(filepath.Dir(target))
if err := os.WriteFile(target, data, 0644); err != nil {
env.t.Fatalf("testutil: could not write fixture file %q: %v", target, err)
}
return target
}
func (env *TestEnv) NewCacher(parts ...string) *filecache.Cacher {
env.t.Helper()
dir := env.CachePath(parts...)
env.mustMkdirAll(dir)
cacher, err := filecache.NewCacher(dir)
if err != nil {
env.t.Fatalf("testutil: could not create cacher: %v", err)
}
return cacher
}
func (env *TestEnv) NewDatabase(name string) *db.Database {
env.t.Helper()
if name == "" {
name = env.cfg.Database.Name
if name == "" {
name = defaultTestDatabaseName
}
}
database, err := db.NewDatabase(env.DataDir, name, env.logger)
if err != nil {
env.t.Fatalf("testutil: could not create database: %v", err)
}
return database
}
func (env *TestEnv) MustNewDatabase(logger *zerolog.Logger) *db.Database {
env.t.Helper()
if logger != nil {
env.logger = logger
}
return env.NewDatabase("")
}
func (env *TestEnv) mustMkdirAll(path string) string {
env.t.Helper()
if err := os.MkdirAll(path, os.ModePerm); err != nil {
env.t.Fatalf("testutil: could not create directory %q: %v", path, err)
}
return path
}
func FixtureRelPath(path string) string {
normalized := strings.ReplaceAll(path, `\`, "/")
if len(normalized) >= 2 && normalized[1] == ':' {
normalized = normalized[2:]
}
cleaned := pathpkg.Clean("/" + normalized)
cleaned = strings.TrimPrefix(cleaned, "/")
if cleaned == "." {
return ""
}
return filepath.FromSlash(cleaned)
}
func NormalizeTestPath(path string) string {
if resolved, err := filepath.EvalSymlinks(path); err == nil {
return filepath.Clean(resolved)
}
return filepath.Clean(path)
}
func sanitizeTestName(name string) string {
if name == "" {
return defaultTestDatabaseName
}
var b strings.Builder
lastDash := false
for _, r := range strings.ToLower(name) {
switch {
case unicode.IsLetter(r), unicode.IsDigit(r):
b.WriteRune(r)
lastDash = false
case !lastDash:
b.WriteByte('-')
lastDash = true
}
}
sanitized := strings.Trim(b.String(), "-")
if sanitized == "" {
return defaultTestDatabaseName
}
return defaultTestDatabaseName + "-" + sanitized
}

View File

@@ -0,0 +1,112 @@
package testutil
import (
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewTestEnv_CreatesIsolatedPaths(t *testing.T) {
first := NewTestEnv(t)
second := NewTestEnv(t)
assert.NotEqual(t, first.RootDir, second.RootDir)
assert.DirExists(t, first.RootDir)
assert.DirExists(t, first.DataDir)
assert.DirExists(t, first.CacheDir)
assert.Equal(t, filepath.Join(first.RootDir, "data"), first.DataDir)
assert.Equal(t, filepath.Join(first.RootDir, "data", "cache"), first.CacheDir)
offlineDir := first.EnsureDataDir("offline", "assets")
assert.Equal(t, filepath.Join(first.DataDir, "offline", "assets"), offlineDir)
assert.DirExists(t, offlineDir)
cacheDir := first.EnsureCacheDir("metadata-provider")
assert.Equal(t, filepath.Join(first.CacheDir, "metadata-provider"), cacheDir)
assert.DirExists(t, cacheDir)
assert.NotNil(t, first.Logger())
assert.NotNil(t, first.Config())
assert.Contains(t, first.Config().Database.Name, "seanime-test-")
}
func TestNewTestEnv_CreatesDatabaseAndCache(t *testing.T) {
env := NewTestEnv(t)
database := env.NewDatabase("")
require.NotNil(t, database)
require.NotNil(t, database.Gorm())
cacher := env.NewCacher("continuity")
require.NotNil(t, cacher)
assert.DirExists(t, env.CachePath("continuity"))
}
func TestNewTestEnv_UsesDefaultsWithoutConfig(t *testing.T) {
t.Setenv("TEST_CONFIG_PATH", t.TempDir())
env := NewTestEnv(t)
cfg := env.Config()
assert.Equal(t, env.DataDir, cfg.Path.DataDir)
assert.Contains(t, cfg.Database.Name, defaultTestDatabaseName)
assert.DirExists(t, env.DataDir)
}
func TestFixtureRelPath(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{
name: "unix absolute path",
input: "/Users/rahim/Downloads/file.mkv",
expected: filepath.Join("Users", "rahim", "Downloads", "file.mkv"),
},
{
name: "windows drive path",
input: `E:\Anime\Series\Ep1.mkv`,
expected: filepath.Join("Anime", "Series", "Ep1.mkv"),
},
{
name: "relative path",
input: "fixture/file.txt",
expected: filepath.Join("fixture", "file.txt"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, FixtureRelPath(tt.input))
})
}
}
func TestEnvMustWriteFixtureFile_RootsUnderTempDir(t *testing.T) {
env := NewTestEnv(t)
target := env.MustWriteFixtureFile("/Users/rahim/Downloads/media.mkv", []byte("fixture"))
require.FileExists(t, target)
assert.Equal(t, env.FixturePath("/Users/rahim/Downloads/media.mkv"), target)
assert.True(t, strings.HasPrefix(target, env.RootDir))
}
func TestRequireSampleVideoPath_UsesEnvOverride(t *testing.T) {
t.Setenv(SampleVideoPathEnv, "/tmp/sample.mkv")
assert.Equal(t, "/tmp/sample.mkv", RequireSampleVideoPath(t))
}
func TestShouldRecordAnilistFixtures(t *testing.T) {
t.Setenv(RecordAnilistFixturesEnvName, "true")
assert.True(t, ShouldRecordAnilistFixtures())
t.Setenv(RecordAnilistFixturesEnvName, "false")
assert.False(t, ShouldRecordAnilistFixtures())
t.Setenv(RecordAnilistFixturesEnvName, "not-a-bool")
assert.False(t, ShouldRecordAnilistFixtures())
}

View File

@@ -7,7 +7,6 @@ import (
"seanime/internal/extension"
hibiketorrent "seanime/internal/extension/hibike/torrent"
"seanime/internal/library/anime"
"seanime/internal/testutil"
itorrent "seanime/internal/torrents/torrent"
"seanime/internal/util"
"seanime/internal/util/filecache"
@@ -19,8 +18,8 @@ import (
"github.com/stretchr/testify/require"
)
// FakeSearchProvider is a fake torrent provider for testing search functionality
type FakeSearchProvider struct {
// TestSearchProvider is a programmable torrent provider for search tests.
type TestSearchProvider struct {
SearchResults map[string][]*hibiketorrent.AnimeTorrent // keyed by resolution
CanSmartSearch bool
SearchCallCount int
@@ -29,7 +28,7 @@ type FakeSearchProvider struct {
LastBatchSetting bool
}
func (f *FakeSearchProvider) Search(opts hibiketorrent.AnimeSearchOptions) ([]*hibiketorrent.AnimeTorrent, error) {
func (f *TestSearchProvider) Search(opts hibiketorrent.AnimeSearchOptions) ([]*hibiketorrent.AnimeTorrent, error) {
f.SearchCallCount++
f.LastSearchQuery = opts.Query
f.LastResolution = ""
@@ -42,7 +41,7 @@ func (f *FakeSearchProvider) Search(opts hibiketorrent.AnimeSearchOptions) ([]*h
return []*hibiketorrent.AnimeTorrent{}, nil
}
func (f *FakeSearchProvider) SmartSearch(opts hibiketorrent.AnimeSmartSearchOptions) ([]*hibiketorrent.AnimeTorrent, error) {
func (f *TestSearchProvider) SmartSearch(opts hibiketorrent.AnimeSmartSearchOptions) ([]*hibiketorrent.AnimeTorrent, error) {
f.SearchCallCount++
f.LastResolution = opts.Resolution
f.LastBatchSetting = opts.Batch
@@ -54,19 +53,19 @@ func (f *FakeSearchProvider) SmartSearch(opts hibiketorrent.AnimeSmartSearchOpti
return []*hibiketorrent.AnimeTorrent{}, nil
}
func (f *FakeSearchProvider) GetTorrentInfoHash(torrent *hibiketorrent.AnimeTorrent) (string, error) {
func (f *TestSearchProvider) GetTorrentInfoHash(torrent *hibiketorrent.AnimeTorrent) (string, error) {
return torrent.InfoHash, nil
}
func (f *FakeSearchProvider) GetTorrentMagnetLink(torrent *hibiketorrent.AnimeTorrent) (string, error) {
func (f *TestSearchProvider) GetTorrentMagnetLink(torrent *hibiketorrent.AnimeTorrent) (string, error) {
return torrent.MagnetLink, nil
}
func (f *FakeSearchProvider) GetLatest() ([]*hibiketorrent.AnimeTorrent, error) {
func (f *TestSearchProvider) GetLatest() ([]*hibiketorrent.AnimeTorrent, error) {
return []*hibiketorrent.AnimeTorrent{}, nil
}
func (f *FakeSearchProvider) GetSettings() hibiketorrent.AnimeProviderSettings {
func (f *TestSearchProvider) GetSettings() hibiketorrent.AnimeProviderSettings {
return hibiketorrent.AnimeProviderSettings{
CanSmartSearch: f.CanSmartSearch,
SmartSearchFilters: nil,
@@ -75,10 +74,10 @@ func (f *FakeSearchProvider) GetSettings() hibiketorrent.AnimeProviderSettings {
}
}
var _ hibiketorrent.AnimeProvider = (*FakeSearchProvider)(nil)
var _ hibiketorrent.AnimeProvider = (*TestSearchProvider)(nil)
// setupTestAutoSelect creates an AutoSelect instance with a fake provider
func setupTestAutoSelect(t *testing.T, provider *FakeSearchProvider) *AutoSelect {
// setupTestAutoSelect creates an AutoSelect instance with a test provider.
func setupTestAutoSelect(t *testing.T, provider *TestSearchProvider) *AutoSelect {
logger := util.NewLogger()
tempDir := t.TempDir()
@@ -87,11 +86,11 @@ func setupTestAutoSelect(t *testing.T, provider *FakeSearchProvider) *AutoSelect
extensionBankRef := util.NewRef(extension.NewUnifiedBank())
// Create fake extension
// Create test extension
ext := extension.NewAnimeTorrentProviderExtension(&extension.Extension{
ID: "fake-provider",
Type: extension.TypeAnimeTorrentProvider,
Name: "Fake Provider",
Name: "Test Provider",
}, provider)
extensionBankRef.Get().Set("fake-provider", ext)
@@ -136,8 +135,6 @@ func createTestMedia(t *testing.T) *anilist.CompleteAnime {
}
func TestSearchFromProvider_SingleResolution(t *testing.T) {
testutil.InitTestProvider(t, testutil.Anilist())
media := createTestMedia(t)
episodeNumber := 1000
@@ -146,7 +143,7 @@ func TestSearchFromProvider_SingleResolution(t *testing.T) {
{Name: "[Erai-raws] One Piece - 1000 [1080p].mkv", InfoHash: "hash2", Seeders: 150},
}
provider := &FakeSearchProvider{
provider := &TestSearchProvider{
SearchResults: map[string][]*hibiketorrent.AnimeTorrent{
"1080p": t1080p,
},
@@ -170,8 +167,6 @@ func TestSearchFromProvider_SingleResolution(t *testing.T) {
}
func TestSearchFromProvider_MultipleResolutions_FirstSucceeds(t *testing.T) {
testutil.InitTestProvider(t, testutil.Anilist())
media := createTestMedia(t)
episodeNumber := 1000
@@ -179,7 +174,7 @@ func TestSearchFromProvider_MultipleResolutions_FirstSucceeds(t *testing.T) {
{Name: "[SubsPlease] One Piece - 1000 (1080p).mkv", InfoHash: "hash1", Seeders: 100},
}
provider := &FakeSearchProvider{
provider := &TestSearchProvider{
SearchResults: map[string][]*hibiketorrent.AnimeTorrent{
"1080p": t1080p,
"720p": {}, // Empty
@@ -204,8 +199,6 @@ func TestSearchFromProvider_MultipleResolutions_FirstSucceeds(t *testing.T) {
}
func TestSearchFromProvider_MultipleResolutions_Fallback(t *testing.T) {
testutil.InitTestProvider(t, testutil.Anilist())
media := createTestMedia(t)
episodeNumber := 1000
@@ -213,7 +206,7 @@ func TestSearchFromProvider_MultipleResolutions_Fallback(t *testing.T) {
{Name: "[SubsPlease] One Piece - 1000 (720p).mkv", InfoHash: "hash1", Seeders: 80},
}
provider := &FakeSearchProvider{
provider := &TestSearchProvider{
SearchResults: map[string][]*hibiketorrent.AnimeTorrent{
"1080p": {}, // Empty, should fallback
"720p": t720p,
@@ -238,12 +231,10 @@ func TestSearchFromProvider_MultipleResolutions_Fallback(t *testing.T) {
}
func TestSearchFromProvider_AllResolutionsFail(t *testing.T) {
testutil.InitTestProvider(t, testutil.Anilist())
media := createTestMedia(t)
episodeNumber := 1000
provider := &FakeSearchProvider{
provider := &TestSearchProvider{
SearchResults: map[string][]*hibiketorrent.AnimeTorrent{
"1080p": {},
"720p": {},
@@ -269,8 +260,6 @@ func TestSearchFromProvider_AllResolutionsFail(t *testing.T) {
}
func TestSearchFromProvider_NoResolutionsInProfile(t *testing.T) {
testutil.InitTestProvider(t, testutil.Anilist())
media := createTestMedia(t)
episodeNumber := 1000
@@ -278,7 +267,7 @@ func TestSearchFromProvider_NoResolutionsInProfile(t *testing.T) {
{Name: "[SubsPlease] One Piece - 1000.mkv", InfoHash: "hash1", Seeders: 100},
}
provider := &FakeSearchProvider{
provider := &TestSearchProvider{
SearchResults: map[string][]*hibiketorrent.AnimeTorrent{
"": tAny, // Empty resolution key for "any"
},
@@ -302,8 +291,6 @@ func TestSearchFromProvider_NoResolutionsInProfile(t *testing.T) {
}
func TestSearchFromProvider_BatchFallback(t *testing.T) {
testutil.InitTestProvider(t, testutil.Anilist())
media := createTestMedia(t)
episodeNumber := 1
@@ -311,7 +298,7 @@ func TestSearchFromProvider_BatchFallback(t *testing.T) {
{Name: "[SubsPlease] One Piece - 01 (1080p).mkv", InfoHash: "hash1", Seeders: 100},
}
provider := &FakeSearchProvider{
provider := &TestSearchProvider{
SearchResults: map[string][]*hibiketorrent.AnimeTorrent{
"1080p": tSingle,
},
@@ -335,8 +322,6 @@ func TestSearchFromProvider_BatchFallback(t *testing.T) {
}
func TestSearchFromProviders_MultipleProviders(t *testing.T) {
testutil.InitTestProvider(t, testutil.Anilist())
media := createTestMedia(t)
episodeNumber := 1000
@@ -345,7 +330,7 @@ func TestSearchFromProviders_MultipleProviders(t *testing.T) {
{Name: "[Erai-raws] One Piece - 1000 [1080p].mkv", InfoHash: "hash2", Seeders: 150},
}
provider := &FakeSearchProvider{
provider := &TestSearchProvider{
SearchResults: map[string][]*hibiketorrent.AnimeTorrent{
"1080p": t1080p,
},
@@ -367,8 +352,6 @@ func TestSearchFromProviders_MultipleProviders(t *testing.T) {
}
func TestSearchFromProviders_Deduplication(t *testing.T) {
testutil.InitTestProvider(t, testutil.Anilist())
media := createTestMedia(t)
episodeNumber := 1000
@@ -378,7 +361,7 @@ func TestSearchFromProviders_Deduplication(t *testing.T) {
{Name: "[Erai-raws] One Piece - 1000 [1080p].mkv", InfoHash: "hash2", Seeders: 150},
}
provider := &FakeSearchProvider{
provider := &TestSearchProvider{
SearchResults: map[string][]*hibiketorrent.AnimeTorrent{
"1080p": t1080p,
},
@@ -400,8 +383,6 @@ func TestSearchFromProviders_Deduplication(t *testing.T) {
}
func TestSearch_Integration(t *testing.T) {
testutil.InitTestProvider(t, testutil.Anilist())
media := createTestMedia(t)
episodeNumber := 1000
@@ -409,7 +390,7 @@ func TestSearch_Integration(t *testing.T) {
{Name: "[SubsPlease] One Piece - 1000 (720p).mkv", InfoHash: "hash1", Seeders: 100},
}
provider := &FakeSearchProvider{
provider := &TestSearchProvider{
SearchResults: map[string][]*hibiketorrent.AnimeTorrent{
"1080p": {}, // Empty, should fallback to 720p
"720p": t720p,
@@ -581,14 +562,14 @@ func TestGetProvidersToSearch(t *testing.T) {
extensionBankRef := util.NewRef(extension.NewUnifiedBank())
// Create fake extensions
provider1 := &FakeSearchProvider{CanSmartSearch: false}
provider1 := &TestSearchProvider{CanSmartSearch: false}
ext1 := extension.NewAnimeTorrentProviderExtension(&extension.Extension{
ID: "provider1",
Type: extension.TypeAnimeTorrentProvider,
Name: "Provider 1",
}, provider1)
provider2 := &FakeSearchProvider{CanSmartSearch: false}
provider2 := &TestSearchProvider{CanSmartSearch: false}
ext2 := extension.NewAnimeTorrentProviderExtension(&extension.Extension{
ID: "provider2",
Type: extension.TypeAnimeTorrentProvider,

View File

@@ -2,24 +2,19 @@ package filecache
import (
"path/filepath"
"seanime/internal/testutil"
"sync"
"testing"
"time"
"github.com/davecgh/go-spew/spew"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCacherFunctions(t *testing.T) {
testutil.InitTestProvider(t)
tempDir := t.TempDir()
t.Log(tempDir)
cacher, err := NewCacher(filepath.Join(tempDir, "cache"))
require.NoError(t, err)
cacher, err := NewCacher(filepath.Join(t.TempDir(), "cache"))
if err != nil {
t.Fatalf("Failed to create cacher: %v", err)
}
bucket := Bucket{
name: "test",
@@ -64,12 +59,7 @@ func TestCacherFunctions(t *testing.T) {
}
func TestCacherSetAndGet(t *testing.T) {
cfg := testutil.InitTestProvider(t)
tempDir := t.TempDir()
t.Log(tempDir)
cacher, err := NewCacher(filepath.Join(cfg.Path.DataDir, "cache"))
cacher, err := NewCacher(filepath.Join(t.TempDir(), "cache"))
bucket := Bucket{
name: "test",

View File

@@ -0,0 +1,96 @@
package main
import (
"flag"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"seanime/internal/testutil"
)
func main() {
var (
runPattern string
count int
verbose bool
dryRun bool
)
flag.StringVar(&runPattern, "run", "", "Optional go test -run pattern for limiting fixture refresh")
flag.IntVar(&count, "count", 1, "go test count value")
flag.BoolVar(&verbose, "v", true, "Run go test with -v")
flag.BoolVar(&dryRun, "dry-run", false, "Print the go test command without executing it")
flag.Parse()
configPath := resolveConfigPath()
if _, err := os.Stat(configPath); err != nil {
fatalf("test config not found at %s; create it from test/config.example.toml first", configPath)
}
cfg := testutil.MustLoadConfig()
if !cfg.Flags.EnableAnilistTests {
fatalf("AniList tests are disabled in %s; set flags.enable_anilist_tests=true", configPath)
}
if strings.TrimSpace(cfg.Provider.AnilistJwt) == "" {
fatalf("provider.anilist_jwt is empty in %s; fixture recording requires an authenticated AniList token", configPath)
}
if strings.TrimSpace(cfg.Provider.AnilistUsername) == "" {
fmt.Fprintf(os.Stderr, "warning: provider.anilist_username is empty in %s; collection-based refresh flows may not cover user-scoped fixtures\n", configPath)
}
packages := flag.Args()
if len(packages) == 0 {
packages = []string{"./internal/api/anilist"}
}
args := []string{"test"}
if verbose {
args = append(args, "-v")
}
if count > 0 {
args = append(args, fmt.Sprintf("-count=%d", count))
}
if runPattern != "" {
args = append(args, "-run", runPattern)
}
args = append(args, packages...)
cmd := exec.Command("go", args...)
cmd.Dir = testutil.ProjectRoot()
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = append(os.Environ(), testutil.RecordAnilistFixturesEnvName+"=true")
fmt.Fprintf(os.Stderr, "Using test config: %s\n", configPath)
fmt.Fprintf(os.Stderr, "Recording AniList fixtures with %s=true\n", testutil.RecordAnilistFixturesEnvName)
fmt.Fprintf(os.Stderr, "Running: go %s\n", strings.Join(args, " "))
if dryRun {
return
}
if err := cmd.Run(); err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
os.Exit(exitErr.ExitCode())
}
fatalf("failed to run go test: %v", err)
}
}
func resolveConfigPath() string {
configDir := os.Getenv("TEST_CONFIG_PATH")
if configDir == "" {
configDir = filepath.Join(testutil.ProjectRoot(), "test")
}
return filepath.Join(configDir, "config.toml")
}
func fatalf(format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, format+"\n", args...)
os.Exit(1)
}

View File

@@ -29,6 +29,7 @@ torbox_api_key = ""
[path]
dataDir = ''
sampleVideoPath = ''
[database]
name = 'seanime-test'

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff