🏑 index : ironmagma/quintodrome.git

package persistence

import (
	"context"
	"path/filepath"
	"testing"

	"github.com/Masterminds/squirrel"
	_ "github.com/mattn/go-sqlite3"
	"github.com/navidrome/navidrome/conf"
	"github.com/navidrome/navidrome/db"
	"github.com/navidrome/navidrome/log"
	"github.com/navidrome/navidrome/model"
	"github.com/navidrome/navidrome/model/request"
	"github.com/navidrome/navidrome/tests"
	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	"github.com/pocketbase/dbx"
)

func TestPersistence(t *testing.T) {
	tests.Init(t, true)

	//os.Remove("./test-123.db")
	//conf.Server.DbPath = "./test-123.db"
	conf.Server.DbPath = "file::memory:?cache=shared&_foreign_keys=on"
	defer db.Init(context.Background())()
	log.SetLevel(log.LevelFatal)
	RegisterFailHandler(Fail)
	RunSpecs(t, "Persistence Suite")
}

func mf(mf model.MediaFile) model.MediaFile {
	mf.Tags = model.Tags{}
	mf.LibraryID = 1
	mf.LibraryPath = "music" // Default folder
	mf.LibraryName = "Music Library"
	mf.Participants = model.Participants{
		model.RoleArtist: model.ParticipantList{
			model.Participant{Artist: model.Artist{ID: mf.ArtistID, Name: mf.Artist}},
		},
	}
	if mf.Lyrics == "" {
		mf.Lyrics = "[]"
	}
	return mf
}

func al(al model.Album) model.Album {
	al.LibraryID = 1
	al.LibraryPath = "music"
	al.LibraryName = "Music Library"
	al.Discs = model.Discs{}
	al.Tags = model.Tags{}
	al.Participants = model.Participants{}
	return al
}

func alWithTags(a model.Album, tags model.Tags) model.Album {
	a = al(a)
	a.Tags = tags
	return a
}

var (
	artistKraftwerk   = model.Artist{ID: "2", Name: "Kraftwerk", OrderArtistName: "kraftwerk"}
	artistBeatles     = model.Artist{ID: "3", Name: "The Beatles", OrderArtistName: "beatles"}
	artistCJK         = model.Artist{ID: "4", Name: "γ‚·γƒΌγƒˆγƒ™γƒ«γƒ„", SortArtistName: "Seatbelts", OrderArtistName: "seatbelts"}
	artistPunctuation = model.Artist{ID: "5", Name: "The Roots", OrderArtistName: "roots"}
	testArtists       = model.Artists{
		artistKraftwerk,
		artistBeatles,
		artistCJK,
		artistPunctuation,
	}
)

var (
	albumSgtPeppers    = al(model.Album{ID: "101", Name: "Sgt Peppers", AlbumArtist: "The Beatles", OrderAlbumName: "sgt peppers", AlbumArtistID: "3", EmbedArtPath: p("beatles/1/sgt/a day.mp3"), SongCount: 1, MaxYear: 1967})
	albumAbbeyRoad     = al(model.Album{ID: "102", Name: "Abbey Road", AlbumArtist: "The Beatles", OrderAlbumName: "abbey road", AlbumArtistID: "3", EmbedArtPath: p("beatles/1/come together.mp3"), SongCount: 1, MaxYear: 1969})
	albumRadioactivity = al(model.Album{ID: "103", Name: "Radioactivity", AlbumArtist: "Kraftwerk", OrderAlbumName: "radioactivity", AlbumArtistID: "2", EmbedArtPath: p("kraft/radio/radio.mp3"), SongCount: 2})
	albumMultiDisc     = al(model.Album{ID: "104", Name: "Multi Disc Album", AlbumArtist: "Test Artist", OrderAlbumName: "multi disc album", AlbumArtistID: "1", EmbedArtPath: p("test/multi/disc1/track1.mp3"), SongCount: 4})
	albumCJK           = al(model.Album{ID: "105", Name: "COWBOY BEBOP", AlbumArtist: "γ‚·γƒΌγƒˆγƒ™γƒ«γƒ„", OrderAlbumName: "cowboy bebop", AlbumArtistID: "4", EmbedArtPath: p("seatbelts/cowboy-bebop/track1.mp3"), SongCount: 1})
	albumWithVersion   = alWithTags(model.Album{ID: "106", Name: "Abbey Road", AlbumArtist: "The Beatles", OrderAlbumName: "abbey road", AlbumArtistID: "3", EmbedArtPath: p("beatles/2/come together.mp3"), SongCount: 1, MaxYear: 2019},
		model.Tags{model.TagAlbumVersion: {"Deluxe Edition"}})
	albumPunctuation = al(model.Album{ID: "107", Name: "Things Fall Apart", AlbumArtist: "The Roots", OrderAlbumName: "things fall apart", AlbumArtistID: "5", EmbedArtPath: p("roots/things/track1.mp3"), SongCount: 1})
	testAlbums       = model.Albums{
		albumSgtPeppers,
		albumAbbeyRoad,
		albumRadioactivity,
		albumMultiDisc,
		albumCJK,
		albumWithVersion,
		albumPunctuation,
	}
)

var (
	songDayInALife    = mf(model.MediaFile{ID: "1001", Title: "A Day In A Life", ArtistID: "3", Artist: "The Beatles", AlbumID: "101", Album: "Sgt Peppers", Path: p("beatles/1/sgt/a day.mp3")})
	songComeTogether  = mf(model.MediaFile{ID: "1002", Title: "Come Together", ArtistID: "3", Artist: "The Beatles", AlbumID: "102", Album: "Abbey Road", Path: p("beatles/1/come together.mp3")})
	songRadioactivity = mf(model.MediaFile{ID: "1003", Title: "Radioactivity", ArtistID: "2", Artist: "Kraftwerk", AlbumID: "103", Album: "Radioactivity", Path: p("kraft/radio/radio.mp3")})
	songAntenna       = mf(model.MediaFile{ID: "1004", Title: "Antenna", ArtistID: "2", Artist: "Kraftwerk",
		AlbumID:     "103",
		Path:        p("kraft/radio/antenna.mp3"),
		RGAlbumGain: new(1.0), RGAlbumPeak: new(2.0), RGTrackGain: new(3.0), RGTrackPeak: new(4.0),
	})
	songAntennaWithLyrics = mf(model.MediaFile{
		ID:       "1005",
		Title:    "Antenna",
		ArtistID: "2",
		Artist:   "Kraftwerk",
		AlbumID:  "103",
		Lyrics:   `[{"lang":"xxx","line":[{"value":"This is a set of lyrics"}],"synced":false}]`,
	})
	songAntenna2 = mf(model.MediaFile{ID: "1006", Title: "Antenna", ArtistID: "2", Artist: "Kraftwerk", AlbumID: "103"})
	// Multi-disc album tracks (intentionally out of order to test sorting)
	songDisc2Track11 = mf(model.MediaFile{ID: "2001", Title: "Disc 2 Track 11", ArtistID: "1", Artist: "Test Artist", AlbumID: "104", Album: "Multi Disc Album", DiscNumber: 2, TrackNumber: 11, Path: p("test/multi/disc2/track11.mp3"), OrderAlbumName: "multi disc album", OrderArtistName: "test artist"})
	songDisc1Track01 = mf(model.MediaFile{ID: "2002", Title: "Disc 1 Track 1", ArtistID: "1", Artist: "Test Artist", AlbumID: "104", Album: "Multi Disc Album", DiscNumber: 1, TrackNumber: 1, Path: p("test/multi/disc1/track1.mp3"), OrderAlbumName: "multi disc album", OrderArtistName: "test artist"})
	songDisc2Track01 = mf(model.MediaFile{ID: "2003", Title: "Disc 2 Track 1", ArtistID: "1", Artist: "Test Artist", AlbumID: "104", Album: "Multi Disc Album", DiscNumber: 2, TrackNumber: 1, Path: p("test/multi/disc2/track1.mp3"), OrderAlbumName: "multi disc album", OrderArtistName: "test artist"})
	songDisc1Track02 = mf(model.MediaFile{ID: "2004", Title: "Disc 1 Track 2", ArtistID: "1", Artist: "Test Artist", AlbumID: "104", Album: "Multi Disc Album", DiscNumber: 1, TrackNumber: 2, Path: p("test/multi/disc1/track2.mp3"), OrderAlbumName: "multi disc album", OrderArtistName: "test artist"})
	songCJK          = mf(model.MediaFile{ID: "3001", Title: "γƒ—γƒ©γƒγƒŠγƒ»γ‚Έγ‚§γƒƒγƒˆ", ArtistID: "4", Artist: "γ‚·γƒΌγƒˆγƒ™γƒ«γƒ„", AlbumID: "105", Album: "COWBOY BEBOP", Path: p("seatbelts/cowboy-bebop/track1.mp3")})
	songVersioned    = mf(model.MediaFile{ID: "3002", Title: "Come Together", ArtistID: "3", Artist: "The Beatles", AlbumID: "106", Album: "Abbey Road", Path: p("beatles/2/come together.mp3")})
	songPunctuation  = mf(model.MediaFile{ID: "3003", Title: "!!!!!!!", ArtistID: "5", Artist: "The Roots", AlbumID: "107", Album: "Things Fall Apart", Path: p("roots/things/track1.mp3")})
	testSongs        = model.MediaFiles{
		songDayInALife,
		songComeTogether,
		songRadioactivity,
		songAntenna,
		songAntennaWithLyrics,
		songAntenna2,
		songDisc2Track11,
		songDisc1Track01,
		songDisc2Track01,
		songDisc1Track02,
		songCJK,
		songVersioned,
		songPunctuation,
	}
)

var (
	radioWithoutHomePage = model.Radio{ID: "1235", StreamUrl: "https://example.com:8000/1/stream.mp3", HomePageUrl: "", Name: "No Homepage"}
	radioWithHomePage    = model.Radio{ID: "5010", StreamUrl: "https://example.com/stream.mp3", Name: "Example Radio", HomePageUrl: "https://example.com"}
	testRadios           = model.Radios{radioWithoutHomePage, radioWithHomePage}
)

var (
	plsBest       model.Playlist
	plsCool       model.Playlist
	testPlaylists []*model.Playlist
)

var (
	adminUser   = model.User{ID: "userid", UserName: "userid", Name: "admin", Email: "admin@email.com", IsAdmin: true}
	regularUser = model.User{ID: "2222", UserName: "regular-user", Name: "Regular User", Email: "regular@example.com"}
	thirdUser   = model.User{ID: "3333", UserName: "third-user", Name: "Third User", Email: "third@example.com"}
	testUsers   = model.Users{adminUser, regularUser, thirdUser}
)

func p(path string) string {
	return filepath.FromSlash(path)
}

var _ = BeforeSuite(func() {
	conn := GetDBXBuilder()
	ctx := log.NewContext(context.TODO())
	ctx = request.WithUser(ctx, adminUser)

	ur := NewUserRepository(ctx, conn)
	for i := range testUsers {
		err := ur.Put(&testUsers[i])
		if err != nil {
			panic(err)
		}
	}

	// Associate users with library 1 (default test library)
	for i := range testUsers {
		err := ur.SetUserLibraries(testUsers[i].ID, []int{1})
		if err != nil {
			panic(err)
		}
	}

	alr := NewAlbumRepository(ctx, conn).(*albumRepository)
	for i := range testAlbums {
		err := alr.Put(new(testAlbums[i]))
		if err != nil {
			panic(err)
		}
	}

	arr := NewArtistRepository(ctx, conn)
	for i := range testArtists {
		err := arr.Put(new(testArtists[i]))
		if err != nil {
			panic(err)
		}
	}

	// Associate artists with library 1 (default test library)
	lr := NewLibraryRepository(ctx, conn)
	for i := range testArtists {
		err := lr.AddArtist(1, testArtists[i].ID)
		if err != nil {
			panic(err)
		}
	}

	// Populate album_artists based on the AlbumArtistID relationships in testAlbums
	artistIDs := map[string]bool{}
	for _, a := range testArtists {
		artistIDs[a.ID] = true
	}
	for i := range testAlbums {
		a := testAlbums[i]
		if a.AlbumArtistID == "" || !artistIDs[a.AlbumArtistID] {
			continue
		}
		_, err := alr.executeSQL(squirrel.Insert("album_artists").SetMap(map[string]any{
			"album_id":  a.ID,
			"artist_id": a.AlbumArtistID,
			"role":      "artist",
			"sub_role":  "",
		}))
		if err != nil {
			panic(err)
		}
	}

	mr := NewMediaFileRepository(ctx, conn)
	for i := range testSongs {
		err := mr.Put(&testSongs[i])
		if err != nil {
			panic(err)
		}
	}

	rar := NewRadioRepository(ctx, conn)
	for i := range testRadios {
		err := rar.Put(new(testRadios[i]))
		if err != nil {
			panic(err)
		}
	}

	plsBest = model.Playlist{
		Name:      "Best",
		Comment:   "No Comments",
		OwnerID:   "userid",
		OwnerName: "userid",
		Public:    true,
		SongCount: 2,
	}
	plsBest.AddMediaFilesByID([]string{"1001", "1003"})
	plsCool = model.Playlist{Name: "Cool", OwnerID: "userid", OwnerName: "userid"}
	plsCool.AddMediaFilesByID([]string{"1004"})
	testPlaylists = []*model.Playlist{&plsBest, &plsCool}

	pr := NewPlaylistRepository(ctx, conn)
	for i := range testPlaylists {
		err := pr.Put(testPlaylists[i])
		if err != nil {
			panic(err)
		}
	}

	// Prepare annotations
	if err := arr.SetStar(true, artistBeatles.ID); err != nil {
		panic(err)
	}
	ar, err := arr.Get(artistBeatles.ID)
	if err != nil {
		panic(err)
	}
	if ar == nil {
		panic("artist not found after SetStar")
	}
	artistBeatles.Starred = true
	artistBeatles.StarredAt = ar.StarredAt
	testArtists[1] = artistBeatles

	if err := alr.SetStar(true, albumRadioactivity.ID); err != nil {
		panic(err)
	}
	al, err := alr.Get(albumRadioactivity.ID)
	if err != nil {
		panic(err)
	}
	if al == nil {
		panic("album not found after SetStar")
	}
	albumRadioactivity.Starred = true
	albumRadioactivity.StarredAt = al.StarredAt
	testAlbums[2] = albumRadioactivity

	if err := mr.SetStar(true, songComeTogether.ID); err != nil {
		panic(err)
	}
	mf, err := mr.Get(songComeTogether.ID)
	if err != nil {
		panic(err)
	}
	songComeTogether.Starred = true
	songComeTogether.StarredAt = mf.StarredAt
	testSongs[1] = songComeTogether
})

func GetDBXBuilder() *dbx.DB {
	return dbx.NewFromDB(db.Db(), db.Dialect)
}