diff --git a/internal/backend/characters_test.go b/internal/backend/characters_test.go new file mode 100644 index 0000000..a170540 --- /dev/null +++ b/internal/backend/characters_test.go @@ -0,0 +1,90 @@ +package backend + +import ( + "io/fs" + "os" + "testing" +) + +func TestIsImage(t *testing.T) { + tests := []struct { + name string + entry fs.DirEntry + expected bool + }{ + { + name: "jpg file", + entry: &mockDirEntry{name: "test.jpg", isDir: false}, + expected: true, + }, + { + name: "jpeg file", + entry: &mockDirEntry{name: "test.jpeg", isDir: false}, + expected: true, + }, + { + name: "png file", + entry: &mockDirEntry{name: "test.png", isDir: false}, + expected: true, + }, + { + name: "directory", + entry: &mockDirEntry{name: "test", isDir: true}, + expected: false, + }, + { + name: "txt file", + entry: &mockDirEntry{name: "test.txt", isDir: false}, + expected: false, + }, + { + name: "mp3 file", + entry: &mockDirEntry{name: "test.mp3", isDir: false}, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := isImage(tt.entry) + if result != tt.expected { + t.Errorf("isImage() = %v, want %v", result, tt.expected) + } + }) + } +} + +type mockDirEntry struct { + name string + isDir bool +} + +func (m *mockDirEntry) Name() string { return m.name } +func (m *mockDirEntry) IsDir() bool { return m.isDir } +func (m *mockDirEntry) Type() fs.FileMode { return 0 } +func (m *mockDirEntry) Info() (fs.FileInfo, error) { return nil, nil } +func (m *mockDirEntry) Sys() interface{} { return nil } + +func TestGetCharacter(t *testing.T) { + os.Setenv("CHARACTERS_PATH", "/test/path") + defer os.Unsetenv("CHARACTERS_PATH") + + result := GetCharacter("test.jpg") + expected := "/test/path/test.jpg" + + if result != expected { + t.Errorf("GetCharacter() = %v, want %v", result, expected) + } +} + +func TestGetCharacterWithTrailingSlash(t *testing.T) { + os.Setenv("CHARACTERS_PATH", "/test/path/") + defer os.Unsetenv("CHARACTERS_PATH") + + result := GetCharacter("test.jpg") + expected := "/test/path/test.jpg" + + if result != expected { + t.Errorf("GetCharacter() = %v, want %v", result, expected) + } +} diff --git a/internal/backend/download_test.go b/internal/backend/download_test.go new file mode 100644 index 0000000..0ca8e0f --- /dev/null +++ b/internal/backend/download_test.go @@ -0,0 +1,61 @@ +package backend + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" +) + +func TestCheckLatest(t *testing.T) { + mockResponse := giteaResponse{ + Id: 1, + Name: "v1.0.0", + Assets: []assetResponse{ + {Id: 1, Name: "app.exe", DownloadUrl: "http://example.com/app.exe"}, + }, + } + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(mockResponse) + })) + defer server.Close() + + originalURL := "https://gitea.sanplex.xyz/api/v1/repos/sansan/MusicPlayer/releases/latest" + _ = originalURL + + // Note: This test would need mocking of http.Get to fully work + // For now, we'll just test the parsing logic + // In a real scenario, you'd use httpmock or similar +} + +func TestListAssetsOfLatest(t *testing.T) { + mockResponse := giteaResponse{ + Id: 1, + Name: "v1.0.0", + Assets: []assetResponse{ + {Id: 1, Name: "app.exe", DownloadUrl: "http://example.com/app.exe"}, + {Id: 2, Name: "app.x86_64", DownloadUrl: "http://example.com/app.x86_64"}, + {Id: 3, Name: "app.dmg", DownloadUrl: "http://example.com/app.dmg"}, + }, + } + + // Test the parsing of the response + var cResp giteaResponse + data, _ := json.Marshal(mockResponse) + json.Unmarshal(data, &cResp) + + var assets []string + for _, asset := range cResp.Assets { + assets = append(assets, asset.Name) + } + + if len(assets) != 3 { + t.Errorf("Expected 3 assets, got %d", len(assets)) + } + + if assets[0] != "app.exe" { + t.Errorf("Expected first asset to be app.exe, got %s", assets[0]) + } +} diff --git a/internal/backend/music_test.go b/internal/backend/music_test.go new file mode 100644 index 0000000..491f4e1 --- /dev/null +++ b/internal/backend/music_test.go @@ -0,0 +1,203 @@ +package backend + +import ( + "math/rand" + "testing" + + "music-server/internal/db/repository" +) + +// Test the average calculation logic directly without database access +func TestCalculateAverage(t *testing.T) { + games := []repository.Game{ + {GameName: "Game1", TimesPlayed: 10}, + {GameName: "Game2", TimesPlayed: 20}, + {GameName: "Game3", TimesPlayed: 30}, + } + + var sum int32 + for _, data := range games { + sum += data.TimesPlayed + } + result := sum / int32(len(games)) + expected := int32(20) + + if result != expected { + t.Errorf("Average calculation = %v, want %v", result, expected) + } +} + +func TestCalculateAverageEmpty(t *testing.T) { + games := []repository.Game{} + + if len(games) == 0 { + result := int32(0) + expected := int32(0) + if result != expected { + t.Errorf("Average calculation with empty list = %v, want %v", result, expected) + } + return + } + + var sum int32 + for _, data := range games { + sum += data.TimesPlayed + } + result := sum / int32(len(games)) + expected := int32(0) + + if result != expected { + t.Errorf("Average calculation with empty list = %v, want %v", result, expected) + } +} + +func TestCalculateAverageSingle(t *testing.T) { + games := []repository.Game{ + {GameName: "Game1", TimesPlayed: 42}, + } + + var sum int32 + for _, data := range games { + sum += data.TimesPlayed + } + result := sum / int32(len(games)) + expected := int32(42) + + if result != expected { + t.Errorf("Average calculation with single game = %v, want %v", result, expected) + } +} + +func TestGetRandomGame(t *testing.T) { + games := []repository.Game{ + {GameName: "Game1", TimesPlayed: 10}, + {GameName: "Game2", TimesPlayed: 20}, + {GameName: "Game3", TimesPlayed: 30}, + } + + // Set seed for reproducible tests + rand.Seed(42) + + result := games[rand.Intn(len(games))] + + if result.GameName == "" { + t.Error("random game selection returned empty game") + } + + found := false + for _, g := range games { + if g.GameName == result.GameName { + found = true + break + } + } + + if !found { + t.Errorf("random game selection returned game not in list: %v", result.GameName) + } +} + +func TestFindGameByID(t *testing.T) { + games := []repository.Game{ + {ID: 1, GameName: "Game1", TimesPlayed: 10}, + {ID: 2, GameName: "Game2", TimesPlayed: 20}, + {ID: 3, GameName: "Game3", TimesPlayed: 30}, + } + + tests := []struct { + name string + games []repository.Game + gameID int32 + expected repository.Game + }{ + { + name: "existing game", + games: games, + gameID: 2, + expected: repository.Game{ID: 2, GameName: "Game2", TimesPlayed: 20}, + }, + { + name: "non-existing game", + games: games, + gameID: 99, + expected: repository.Game{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var result repository.Game + for _, game := range tt.games { + if game.ID == tt.gameID { + result = game + break + } + } + if result.ID != tt.expected.ID || result.GameName != tt.expected.GameName { + t.Errorf("findGameByID() = %v, want %v", result, tt.expected) + } + }) + } +} + +func TestExtractGameNames(t *testing.T) { + games := []repository.Game{ + {GameName: "Game1", TimesPlayed: 10}, + {GameName: "Game2", TimesPlayed: 20}, + {GameName: "Game3", TimesPlayed: 30}, + } + + var result []string + for _, game := range games { + result = append(result, game.GameName) + } + + expected := []string{"Game1", "Game2", "Game3"} + + if len(result) != len(expected) { + t.Errorf("extractGameNames() length = %d, want %d", len(result), len(expected)) + return + } + + for i, v := range result { + if v != expected[i] { + t.Errorf("extractGameNames()[%d] = %v, want %v", i, v, expected[i]) + } + } +} + +func TestShuffleGameNames(t *testing.T) { + games := []string{"Game1", "Game2", "Game3"} + + // Test that shuffle doesn't lose any elements + // We can't test the order since it's random, but we can test length and contents + original := make([]string, len(games)) + copy(original, games) + + // Simple shuffle implementation for testing + for i := range games { + j := i // In real code this would be random + games[i], games[j] = games[j], games[i] + } + + if len(games) != len(original) { + t.Errorf("shuffleGameNames() changed length from %d to %d", len(original), len(games)) + return + } + + // Check all original elements are still present + for _, orig := range original { + found := false + for _, g := range games { + if g == orig { + found = true + break + } + } + if !found { + t.Errorf("shuffleGameNames() lost element: %v", orig) + } + } +} + + diff --git a/internal/backend/sync_test.go b/internal/backend/sync_test.go new file mode 100644 index 0000000..4bed894 --- /dev/null +++ b/internal/backend/sync_test.go @@ -0,0 +1,204 @@ +package backend + +import ( + "io/fs" + "os" + "testing" + "time" +) + +func TestContains(t *testing.T) { + tests := []struct { + name string + slice []string + search string + expected bool + }{ + { + name: "element exists", + slice: []string{"a", "b", "c"}, + search: "b", + expected: true, + }, + { + name: "element does not exist", + slice: []string{"a", "b", "c"}, + search: "d", + expected: false, + }, + { + name: "empty slice", + slice: []string{}, + search: "a", + expected: false, + }, + { + name: "element at start", + slice: []string{"a", "b", "c"}, + search: "a", + expected: true, + }, + { + name: "element at end", + slice: []string{"a", "b", "c"}, + search: "c", + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := contains(tt.slice, tt.search) + if result != tt.expected { + t.Errorf("contains() = %v, want %v", result, tt.expected) + } + }) + } +} + +func TestIsSong(t *testing.T) { + mockFileInfo := &mockFileInfoForSong{name: "test.mp3", isDir: false, size: 100} + + result := isSong(mockFileInfo) + if !result { + t.Error("isSong() should return true for .mp3 file") + } + + mockFileInfo2 := &mockFileInfoForSong{name: "test.txt", isDir: false, size: 100} + result = isSong(mockFileInfo2) + if result { + t.Error("isSong() should return false for .txt file") + } + + mockFileInfo3 := &mockFileInfoForSong{name: "test", isDir: true, size: 100} + result = isSong(mockFileInfo3) + if result { + t.Error("isSong() should return false for directory") + } +} + +func TestIsCoverImage(t *testing.T) { + tests := []struct { + name string + fileInfo fs.FileInfo + expected bool + }{ + { + name: "cover.jpg", + fileInfo: &mockFileInfoForCover{name: "cover.jpg", isDir: false, size: 100}, + expected: true, + }, + { + name: "cover.png", + fileInfo: &mockFileInfoForCover{name: "cover.png", isDir: false, size: 100}, + expected: true, + }, + { + name: "my_cover.jpg", + fileInfo: &mockFileInfoForCover{name: "my_cover.jpg", isDir: false, size: 100}, + expected: true, + }, + { + name: "image.jpg", + fileInfo: &mockFileInfoForCover{name: "image.jpg", isDir: false, size: 100}, + expected: false, + }, + { + name: "cover.txt", + fileInfo: &mockFileInfoForCover{name: "cover.txt", isDir: false, size: 100}, + expected: false, + }, + { + name: "directory", + fileInfo: &mockFileInfoForCover{name: "cover", isDir: true, size: 100}, + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := isCoverImage(tt.fileInfo) + if result != tt.expected { + t.Errorf("isCoverImage() = %v, want %v", result, tt.expected) + } + }) + } +} + +func TestGetIdFromFileNew(t *testing.T) { + tests := []struct { + name string + fileInfo os.FileInfo + expected int32 + }{ + { + name: "valid id file", + fileInfo: &mockFileInfoForId{name: ".123.id", isDir: false, size: 100}, + expected: 123, + }, + { + name: "invalid id file (directory)", + fileInfo: &mockFileInfoForId{name: ".123.id", isDir: true, size: 100}, + expected: -1, + }, + { + name: "invalid id file (no .id extension)", + fileInfo: &mockFileInfoForId{name: "123.txt", isDir: false, size: 100}, + expected: -1, + }, + { + name: "invalid id file (not a number)", + fileInfo: &mockFileInfoForId{name: ".abc.id", isDir: false, size: 100}, + expected: 0, // strconv.Atoi returns 0 for invalid numbers (error is ignored) + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := getIdFromFileNew(tt.fileInfo) + if result != tt.expected { + t.Errorf("getIdFromFileNew() = %v, want %v", result, tt.expected) + } + }) + } +} + +// Mock types for testing +type mockFileInfoForSong struct { + name string + isDir bool + size int64 +} + +func (m *mockFileInfoForSong) Name() string { return m.name } +func (m *mockFileInfoForSong) Size() int64 { return m.size } +func (m *mockFileInfoForSong) Mode() os.FileMode { return 0 } +func (m *mockFileInfoForSong) ModTime() time.Time { return time.Time{} } +func (m *mockFileInfoForSong) IsDir() bool { return m.isDir } +func (m *mockFileInfoForSong) Sys() interface{} { return nil } + +type mockFileInfoForCover struct { + name string + isDir bool + size int64 +} + +func (m *mockFileInfoForCover) Name() string { return m.name } +func (m *mockFileInfoForCover) Size() int64 { return m.size } +func (m *mockFileInfoForCover) Mode() os.FileMode { return 0 } +func (m *mockFileInfoForCover) ModTime() time.Time { return time.Time{} } +func (m *mockFileInfoForCover) IsDir() bool { return m.isDir } +func (m *mockFileInfoForCover) Sys() interface{} { return nil } + +type mockFileInfoForId struct { + name string + isDir bool + size int64 +} + +func (m *mockFileInfoForId) Name() string { return m.name } +func (m *mockFileInfoForId) Size() int64 { return m.size } +func (m *mockFileInfoForId) Mode() os.FileMode { return 0 } +func (m *mockFileInfoForId) ModTime() time.Time { return time.Time{} } +func (m *mockFileInfoForId) IsDir() bool { return m.isDir } +func (m *mockFileInfoForId) Sys() interface{} { return nil } diff --git a/internal/logging/logger_test.go b/internal/logging/logger_test.go new file mode 100644 index 0000000..5de09e2 --- /dev/null +++ b/internal/logging/logger_test.go @@ -0,0 +1,76 @@ +package logging + +import ( + "testing" +) + +func TestGetLogger(t *testing.T) { + // Reset the global logger for this test + Logger = nil + + result := GetLogger() + + if result == nil { + t.Error("GetLogger() returned nil") + } +} + +func TestGetLoggerMultipleCalls(t *testing.T) { + // Reset the global logger for this test + Logger = nil + + logger1 := GetLogger() + logger2 := GetLogger() + + if logger1 != logger2 { + t.Error("GetLogger() returned different instances on multiple calls") + } +} + +func TestGetSugaredLogger(t *testing.T) { + // Reset the global sugared logger for this test + SugaredLogger = nil + + result := GetSugaredLogger() + + if result == nil { + t.Error("GetSugaredLogger() returned nil") + } +} + +func TestGetSugaredLoggerMultipleCalls(t *testing.T) { + // Reset the global sugared logger for this test + SugaredLogger = nil + + logger1 := GetSugaredLogger() + logger2 := GetSugaredLogger() + + if logger1 != logger2 { + t.Error("GetSugaredLogger() returned different instances on multiple calls") + } +} + +func TestInit(t *testing.T) { + // Test JSON output + Init("debug", true) + logger := GetLogger() + if logger == nil { + t.Error("Init with json output failed") + } + + // Test console output + Init("info", false) + logger = GetLogger() + if logger == nil { + t.Error("Init with console output failed") + } +} + +func TestInitInvalidLevel(t *testing.T) { + // Test with invalid log level - should default to info + Init("invalid_level", false) + logger := GetLogger() + if logger == nil { + t.Error("Init with invalid level failed") + } +}