From 563efe78bd4bdc1de79311d811d4e5645a725358 Mon Sep 17 00:00:00 2001 From: Mathis Maquenne <124215603+mathismqn@users.noreply.github.com> Date: Wed, 5 Aug 2026 11:49:21 +0200 Subject: [PATCH] test: add deezer crypto and filename tests --- internal/deezer/blowfish_test.go | 56 ++++++++++++++++ internal/deezer/ecb_test.go | 72 ++++++++++++++++++++ internal/deezer/track_test.go | 109 +++++++++++++++++++++++++++++++ 3 files changed, 237 insertions(+) create mode 100644 internal/deezer/blowfish_test.go create mode 100644 internal/deezer/ecb_test.go create mode 100644 internal/deezer/track_test.go diff --git a/internal/deezer/blowfish_test.go b/internal/deezer/blowfish_test.go new file mode 100644 index 0000000..e8b2858 --- /dev/null +++ b/internal/deezer/blowfish_test.go @@ -0,0 +1,56 @@ +package deezer + +import ( + "bytes" + "crypto/cipher" + "encoding/hex" + "testing" + + "golang.org/x/crypto/blowfish" +) + +func TestBlowfishKey(t *testing.T) { + tests := []struct { + trackID string + want string + }{ + {"3135556", "6c6c666b39662c37652575603c643439"}, + {"123456789", "6d34656061377f31322a7336393f626b"}, + {"1", "3464656e343a7d3a672c236a33696061"}, + } + + for _, tt := range tests { + got := hex.EncodeToString(BlowfishKey(tt.trackID)) + if got != tt.want { + t.Errorf("BlowfishKey(%q) = %s, want %s", tt.trackID, got, tt.want) + } + } +} + +func TestDecryptBlowfishRoundTrip(t *testing.T) { + key := BlowfishKey("3135556") + plaintext := bytes.Repeat([]byte("01234567"), 16) + + block, err := blowfish.NewCipher(key) + if err != nil { + t.Fatalf("NewCipher: %v", err) + } + + encrypted := make([]byte, len(plaintext)) + cipher.NewCBCEncrypter(block, blowfishIV).CryptBlocks(encrypted, plaintext) + + decrypted, err := DecryptBlowfish(encrypted, key) + if err != nil { + t.Fatalf("DecryptBlowfish: %v", err) + } + + if !bytes.Equal(decrypted, plaintext) { + t.Errorf("round trip mismatch: got %x, want %x", decrypted, plaintext) + } +} + +func TestDecryptBlowfishInvalidKey(t *testing.T) { + if _, err := DecryptBlowfish(make([]byte, 8), nil); err == nil { + t.Error("expected error for empty key") + } +} diff --git a/internal/deezer/ecb_test.go b/internal/deezer/ecb_test.go new file mode 100644 index 0000000..d3f0b5c --- /dev/null +++ b/internal/deezer/ecb_test.go @@ -0,0 +1,72 @@ +package deezer + +import ( + "bytes" + "testing" +) + +func TestZeroPad(t *testing.T) { + tests := []struct { + length int + want int + }{ + {0, 0}, + {1, 16}, + {15, 16}, + {16, 16}, + {17, 32}, + } + + for _, tt := range tests { + padded := zeroPad(make([]byte, tt.length)) + if len(padded) != tt.want { + t.Errorf("zeroPad(len %d) = len %d, want %d", tt.length, len(padded), tt.want) + } + } +} + +func TestZeroPadPreservesData(t *testing.T) { + data := []byte("secret") + padded := zeroPad(data) + + if !bytes.Equal(padded[:len(data)], data) { + t.Errorf("zeroPad changed data: got %q", padded[:len(data)]) + } + for _, b := range padded[len(data):] { + if b != 0 { + t.Errorf("padding is not zero: %v", padded) + } + } +} + +func TestECBRoundTrip(t *testing.T) { + key := []byte("0123456789abcdef") + plaintext := zeroPad([]byte("some secret data")) + + encrypted, err := ecbEncrypt(key, plaintext) + if err != nil { + t.Fatalf("ecbEncrypt: %v", err) + } + if bytes.Equal(encrypted, plaintext) { + t.Fatal("encrypted data equals plaintext") + } + + decrypted, err := ecbDecrypt(key, encrypted) + if err != nil { + t.Fatalf("ecbDecrypt: %v", err) + } + if !bytes.Equal(decrypted, plaintext) { + t.Errorf("round trip mismatch: got %q, want %q", decrypted, plaintext) + } +} + +func TestECBRejectsPartialBlock(t *testing.T) { + key := []byte("0123456789abcdef") + + if _, err := ecbEncrypt(key, make([]byte, 15)); err == nil { + t.Error("expected error for data not a multiple of the block size") + } + if _, err := ecbDecrypt(key, make([]byte, 17)); err == nil { + t.Error("expected error for data not a multiple of the block size") + } +} diff --git a/internal/deezer/track_test.go b/internal/deezer/track_test.go new file mode 100644 index 0000000..44d3af4 --- /dev/null +++ b/internal/deezer/track_test.go @@ -0,0 +1,109 @@ +package deezer + +import ( + "encoding/json" + "strings" + "testing" +) + +func TestFullTitle(t *testing.T) { + tests := []struct { + title string + version string + want string + }{ + {"Song", "", "Song"}, + {"Song", "(Remix)", "Song (Remix)"}, + } + + for _, tt := range tests { + track := &Track{Title: tt.title, Version: tt.version} + if got := track.FullTitle(); got != tt.want { + t.Errorf("FullTitle() = %q, want %q", got, tt.want) + } + } +} + +func TestFilename(t *testing.T) { + tests := []struct { + name string + track Track + kind Kind + mediaFormat string + want string + }{ + { + name: "album with numeric track number", + track: Track{Artist: "Artist", Title: "Song", TrackNumber: "1"}, + kind: KindAlbum, + mediaFormat: "MP3_320", + want: "01. Artist - Song.mp3", + }, + { + name: "album with non-numeric track number", + track: Track{Artist: "Artist", Title: "Song", TrackNumber: "A"}, + kind: KindAlbum, + mediaFormat: "MP3_320", + want: "A. Artist - Song.mp3", + }, + { + name: "playlist has no prefix", + track: Track{Artist: "Artist", Title: "Song", TrackNumber: "1"}, + kind: KindPlaylist, + mediaFormat: "MP3_128", + want: "Artist - Song.mp3", + }, + { + name: "flac extension", + track: Track{Artist: "Artist", Title: "Song"}, + kind: KindTrack, + mediaFormat: "FLAC", + want: "Artist - Song.flac", + }, + { + name: "version appended", + track: Track{Artist: "Artist", Title: "Song", Version: "(Live)"}, + kind: KindTrack, + mediaFormat: "MP3_320", + want: "Artist - Song (Live).mp3", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.track.Filename(tt.kind, tt.mediaFormat); got != tt.want { + t.Errorf("Filename() = %q, want %q", got, tt.want) + } + }) + } +} + +func TestFilenameSanitizesSeparators(t *testing.T) { + track := Track{Artist: "AC/DC", Title: "Song"} + got := track.Filename(KindTrack, "MP3_320") + + if strings.ContainsRune(got, '/') { + t.Errorf("Filename() contains a path separator: %q", got) + } + if !strings.HasSuffix(got, ".mp3") { + t.Errorf("Filename() = %q, want .mp3 suffix", got) + } +} + +func TestContributorsUnmarshalJSON(t *testing.T) { + var c Contributors + if err := json.Unmarshal([]byte("[]"), &c); err != nil { + t.Fatalf("unmarshal empty array: %v", err) + } + if len(c.MainArtists) != 0 || len(c.Composers) != 0 || len(c.Authors) != 0 { + t.Errorf("expected empty contributors, got %+v", c) + } + + data := `{"main_artist":["A","B"],"composer":["C"],"author":["D"]}` + if err := json.Unmarshal([]byte(data), &c); err != nil { + t.Fatalf("unmarshal object: %v", err) + } + if len(c.MainArtists) != 2 || c.MainArtists[0] != "A" || len(c.Composers) != 1 || len(c.Authors) != 1 { + t.Errorf("unexpected contributors: %+v", c) + } +}