Migrate ym-dl from custom oapi-codegen Yandex Music API client to external gitea.mrixs.me/Mrixs/yandex-music-api-go library with token-only authentication. Key changes: - Replace custom Yandex API client with yandexmusic library wrapper - Token-based OAuth authentication only (no username/password fallback) - SQLite storage index with JSON index migration path - Extended ID3 tag support: genre, comment, lyrics, publisher, ISRC, copyright - High-resolution album covers (1000x1000) - Fixed album download check (requires all tracks, not just one)
291 lines
7.0 KiB
Go
291 lines
7.0 KiB
Go
package storage
|
|
|
|
import (
|
|
"path/filepath"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.mrixs.me/Mrixs/yandex-music-api-go/yandexmusic"
|
|
)
|
|
|
|
func TestStorageNew(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
s, err := NewStorage(tmpDir, true)
|
|
if err != nil {
|
|
t.Fatalf("NewStorage failed: %v", err)
|
|
}
|
|
if s == nil {
|
|
t.Fatal("expected non-nil storage")
|
|
}
|
|
}
|
|
|
|
func TestStorageSaveAndLoadTrack(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
s, err := NewStorage(tmpDir, true)
|
|
if err != nil {
|
|
t.Fatalf("NewStorage failed: %v", err)
|
|
}
|
|
|
|
meta := TrackMeta{
|
|
TrackID: "123",
|
|
AlbumID: "456",
|
|
ArtistID: "789",
|
|
Title: "Test Track",
|
|
ArtistName: "Test Artist",
|
|
AlbumTitle: "Test Album",
|
|
TrackNumber: 1,
|
|
DiscNumber: 1,
|
|
DurationMs: 180000,
|
|
Bitrate: 320,
|
|
Codec: "mp3",
|
|
DownloadedAt: time.Now().Format(time.RFC3339),
|
|
}
|
|
|
|
data := []byte("fake audio data")
|
|
|
|
if err := s.SaveTrack(meta, data, nil); err != nil {
|
|
t.Fatalf("SaveTrack failed: %v", err)
|
|
}
|
|
|
|
// Check index
|
|
if !s.IsTrackDownloaded("123") {
|
|
t.Error("track not marked as downloaded")
|
|
}
|
|
|
|
loadedMeta, ok := s.GetTrackMeta("123")
|
|
if !ok {
|
|
t.Fatal("track not found in index")
|
|
}
|
|
if loadedMeta.Title != "Test Track" {
|
|
t.Errorf("expected title 'Test Track', got '%s'", loadedMeta.Title)
|
|
}
|
|
if loadedMeta.FileSize != int64(len(data)) {
|
|
t.Errorf("expected file size %d, got %d", len(data), loadedMeta.FileSize)
|
|
}
|
|
// Check file exists using the stored path
|
|
if _, err := os.Stat(loadedMeta.FilePath); os.IsNotExist(err) {
|
|
t.Error("track file not created")
|
|
}
|
|
}
|
|
|
|
func TestStorageIdempotent(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
s, err := NewStorage(tmpDir, true)
|
|
if err != nil {
|
|
t.Fatalf("NewStorage failed: %v", err)
|
|
}
|
|
|
|
meta := TrackMeta{
|
|
TrackID: "123",
|
|
Title: "Test Track",
|
|
ArtistName: "Test Artist",
|
|
AlbumTitle: "Test Album",
|
|
TrackNumber: 1,
|
|
Codec: "mp3",
|
|
}
|
|
|
|
data := []byte("fake audio data")
|
|
|
|
// Save first time
|
|
if err := s.SaveTrack(meta, data, nil); err != nil {
|
|
t.Fatalf("first SaveTrack failed: %v", err)
|
|
}
|
|
|
|
firstPath := meta.FilePath
|
|
|
|
// Save second time (should not create duplicate)
|
|
if err := s.SaveTrack(meta, data, nil); err != nil {
|
|
t.Fatalf("second SaveTrack failed: %v", err)
|
|
}
|
|
|
|
if meta.FilePath != firstPath {
|
|
t.Errorf("file path changed on second save: %s vs %s", meta.FilePath, firstPath)
|
|
}
|
|
|
|
// Verify only one file exists
|
|
files, _ := os.ReadDir(filepath.Join(tmpDir, "Test Artist", "Test Album"))
|
|
count := 0
|
|
for _, f := range files {
|
|
if !f.IsDir() {
|
|
count++
|
|
}
|
|
}
|
|
if count != 1 {
|
|
t.Errorf("expected 1 file, found %d", count)
|
|
}
|
|
}
|
|
|
|
func TestStorageOrganizeFalse(t *testing.T) {
|
|
tmpDir := t.TempDir()
|
|
|
|
s, err := NewStorage(tmpDir, false)
|
|
if err != nil {
|
|
t.Fatalf("NewStorage failed: %v", err)
|
|
}
|
|
|
|
meta := TrackMeta{
|
|
TrackID: "123",
|
|
Title: "Test Track",
|
|
ArtistName: "Test Artist",
|
|
AlbumTitle: "Test Album",
|
|
TrackNumber: 1,
|
|
Codec: "mp3",
|
|
DownloadedAt: time.Now().Format(time.RFC3339),
|
|
}
|
|
|
|
data := []byte("fake audio data")
|
|
|
|
if err := s.SaveTrack(meta, data, nil); err != nil {
|
|
t.Fatalf("SaveTrack failed: %v", err)
|
|
}
|
|
|
|
// Should be flat structure - get the saved meta from index
|
|
loadedMeta, ok := s.GetTrackMeta("123")
|
|
if !ok {
|
|
t.Fatal("track not found in index")
|
|
}
|
|
expectedDir := tmpDir
|
|
if filepath.Dir(loadedMeta.FilePath) != expectedDir {
|
|
t.Errorf("expected flat structure in %s, got %s", expectedDir, filepath.Dir(loadedMeta.FilePath))
|
|
}
|
|
}
|
|
|
|
func TestStorageSanitizeFilename(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
expected string
|
|
}{
|
|
{"normal", "normal"},
|
|
{"with/slash", "with_slash"},
|
|
{"with\\backslash", "with_backslash"},
|
|
{"with:colon", "with_colon"},
|
|
{"with*star", "with_star"},
|
|
{"with?question", "with_question"},
|
|
{"with\"quote", "with_quote"},
|
|
{"with<lt", "with_lt"},
|
|
{"with>gt", "with_gt"},
|
|
{"with|pipe", "with_pipe"},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
result := sanitizeFilename(tc.input)
|
|
if result != tc.expected {
|
|
t.Errorf("sanitizeFilename(%q) = %q, want %q", tc.input, result, tc.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStorageBuildTrackMetaFromYandex(t *testing.T) {
|
|
track := &yandexmusic.Track{
|
|
ID: "123",
|
|
Title: "Test Track",
|
|
Genre: "Pop",
|
|
DurationMs: 180000,
|
|
Artists: []yandexmusic.Artist{
|
|
{ID: "789", Name: "Test Artist"},
|
|
},
|
|
Albums: []yandexmusic.Album{
|
|
{ID: "456", Title: "Test Album"},
|
|
},
|
|
}
|
|
|
|
album := &yandexmusic.Album{
|
|
ID: "456",
|
|
Title: "Test Album",
|
|
Genre: "Album Pop",
|
|
Year: 2023,
|
|
ReleaseDate: "2023-05-15",
|
|
Label: "Test Label",
|
|
Description: "Test album description",
|
|
CatalogNumber: "TC-001",
|
|
Artists: []yandexmusic.Artist{
|
|
{ID: "789", Name: "Test Artist"},
|
|
},
|
|
}
|
|
|
|
downloadInfo := yandexmusic.DownloadInfo{
|
|
Bitrate: 320,
|
|
Codec: "mp3",
|
|
}
|
|
|
|
meta := BuildTrackMetaFromYandex(track, album, 1, 1, downloadInfo)
|
|
|
|
if meta.TrackID != "123" {
|
|
t.Errorf("expected track ID 123, got %s", meta.TrackID)
|
|
}
|
|
if meta.Title != "Test Track" {
|
|
t.Errorf("expected title 'Test Track', got '%s'", meta.Title)
|
|
}
|
|
if meta.ArtistName != "Test Artist" {
|
|
t.Errorf("expected artist 'Test Artist', got '%s'", meta.ArtistName)
|
|
}
|
|
if meta.AlbumTitle != "Test Album" {
|
|
t.Errorf("expected album 'Test Album', got '%s'", meta.AlbumTitle)
|
|
}
|
|
if meta.Bitrate != 320 {
|
|
t.Errorf("expected bitrate 320, got %d", meta.Bitrate)
|
|
}
|
|
if meta.Codec != "mp3" {
|
|
t.Errorf("expected codec mp3, got %s", meta.Codec)
|
|
}
|
|
// Extended metadata checks
|
|
if meta.Genre != "Pop" {
|
|
t.Errorf("expected genre 'Pop', got '%s'", meta.Genre)
|
|
}
|
|
if meta.Comment != "Test album description" {
|
|
t.Errorf("expected comment 'Test album description', got '%s'", meta.Comment)
|
|
}
|
|
if meta.Lyrics != "" {
|
|
t.Errorf("expected empty lyrics (no lyrics in track), got '%s'", meta.Lyrics)
|
|
}
|
|
if meta.Publisher != "Test Label" {
|
|
t.Errorf("expected publisher 'Test Label', got '%s'", meta.Publisher)
|
|
}
|
|
if meta.EncodedBy != "ym-dl" {
|
|
t.Errorf("expected encoded_by 'ym-dl', got '%s'", meta.EncodedBy)
|
|
}
|
|
if meta.OriginalYear != "2023" {
|
|
t.Errorf("expected original_year '2023', got '%s'", meta.OriginalYear)
|
|
}
|
|
if meta.Copyright != "TC-001" {
|
|
t.Errorf("expected copyright 'TC-001', got '%s'", meta.Copyright)
|
|
}
|
|
}
|
|
|
|
func TestStorageBuildAlbumMetaFromYandex(t *testing.T) {
|
|
album := &yandexmusic.Album{
|
|
ID: "456",
|
|
Title: "Test Album",
|
|
Artists: []yandexmusic.Artist{
|
|
{ID: "789", Name: "Test Artist"},
|
|
},
|
|
Year: 2023,
|
|
}
|
|
|
|
tracks := []TrackMeta{
|
|
{TrackID: "1", Title: "Track 1"},
|
|
{TrackID: "2", Title: "Track 2"},
|
|
}
|
|
|
|
meta := BuildAlbumMetaFromYandex(album, tracks)
|
|
|
|
if meta.AlbumID != "456" {
|
|
t.Errorf("expected album ID 456, got %s", meta.AlbumID)
|
|
}
|
|
if meta.Title != "Test Album" {
|
|
t.Errorf("expected title 'Test Album', got '%s'", meta.Title)
|
|
}
|
|
if meta.ArtistName != "Test Artist" {
|
|
t.Errorf("expected artist 'Test Artist', got '%s'", meta.ArtistName)
|
|
}
|
|
if meta.Year != 2023 {
|
|
t.Errorf("expected year 2023, got %d", meta.Year)
|
|
}
|
|
if meta.TrackCount != 2 {
|
|
t.Errorf("expected track count 2, got %d", meta.TrackCount)
|
|
}
|
|
} |