Web-based Winamp controller for CarPC � Go backend, mobile-first UI
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

187 rindas
4.6KB

  1. package rating
  2. import (
  3. "bytes"
  4. "os"
  5. "testing"
  6. )
  7. // buildMinimalMP3 returns a minimal valid MP3: a 10-byte ID3v2.3 header with
  8. // no frames (tag size = 0) followed by 4096 bytes of fake audio data (0xFF 0xFB
  9. // sync word repeated, which is close enough for our purposes).
  10. func buildMinimalMP3() []byte {
  11. var buf bytes.Buffer
  12. // ID3v2.3 header: "ID3" + version 2.3.0 + flags 0 + synchsafe size 0
  13. buf.Write([]byte{'I', 'D', '3', 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
  14. // Fake audio data
  15. audio := make([]byte, 4096)
  16. for i := range audio {
  17. audio[i] = byte(i & 0xFF)
  18. }
  19. buf.Write(audio)
  20. return buf.Bytes()
  21. }
  22. func writeTempMP3(t *testing.T, data []byte) string {
  23. t.Helper()
  24. f, err := os.CreateTemp(t.TempDir(), "test-*.mp3")
  25. if err != nil {
  26. t.Fatalf("create temp: %v", err)
  27. }
  28. if _, err := f.Write(data); err != nil {
  29. t.Fatalf("write temp: %v", err)
  30. }
  31. f.Close()
  32. return f.Name()
  33. }
  34. func TestPopmToStars(t *testing.T) {
  35. cases := []struct{ in uint8; want int }{
  36. {0, 0}, {1, 1}, {31, 1}, {32, 2}, {95, 2},
  37. {96, 3}, {127, 3}, {128, 3}, {159, 3},
  38. {160, 4}, {195, 4}, {196, 4}, {223, 4},
  39. {224, 5}, {255, 5},
  40. }
  41. for _, c := range cases {
  42. got := popmToStars(c.in)
  43. if got != c.want {
  44. t.Errorf("popmToStars(%d) = %d, want %d", c.in, got, c.want)
  45. }
  46. }
  47. }
  48. func TestStarsToPOPM(t *testing.T) {
  49. want := [6]uint8{0, 1, 64, 128, 196, 255}
  50. for i, w := range want {
  51. got := starsToPOPM(i)
  52. if got != w {
  53. t.Errorf("starsToPOPM(%d) = %d, want %d", i, got, w)
  54. }
  55. }
  56. }
  57. func TestId3v2AudioStart_noTag(t *testing.T) {
  58. // File with no ID3 header
  59. f, err := os.CreateTemp(t.TempDir(), "noTag-*.mp3")
  60. if err != nil {
  61. t.Fatal(err)
  62. }
  63. f.Write([]byte{0xFF, 0xFB, 0x90, 0x00}) // MP3 sync
  64. f.Close()
  65. start, err := id3v2AudioStart(f.Name())
  66. if err != nil {
  67. t.Fatalf("unexpected error: %v", err)
  68. }
  69. if start != 0 {
  70. t.Errorf("got audioStart=%d, want 0", start)
  71. }
  72. }
  73. func TestId3v2AudioStart_withHeader(t *testing.T) {
  74. // Header with tag size 0 → audioStart should be 10
  75. data := buildMinimalMP3()
  76. path := writeTempMP3(t, data)
  77. start, err := id3v2AudioStart(path)
  78. if err != nil {
  79. t.Fatalf("unexpected error: %v", err)
  80. }
  81. if start != 10 {
  82. t.Errorf("got audioStart=%d, want 10", start)
  83. }
  84. }
  85. func TestSetAndGet_roundtrip(t *testing.T) {
  86. original := buildMinimalMP3()
  87. path := writeTempMP3(t, original)
  88. // File should start with no rating
  89. r, err := Get(path)
  90. if err != nil {
  91. t.Fatalf("Get before Set: %v", err)
  92. }
  93. if r != 0 {
  94. t.Errorf("expected 0 before Set, got %d", r)
  95. }
  96. // Set each star value and read back
  97. for stars := 1; stars <= 5; stars++ {
  98. if err := Set(path, stars); err != nil {
  99. t.Fatalf("Set(%d): %v", stars, err)
  100. }
  101. // File must not be truncated — must be larger than tag-only (> original size)
  102. info, err := os.Stat(path)
  103. if err != nil {
  104. t.Fatalf("stat after Set(%d): %v", stars, err)
  105. }
  106. if info.Size() < int64(len(original)) {
  107. t.Errorf("Set(%d) shrunk file: got %d bytes, original was %d",
  108. stars, info.Size(), len(original))
  109. }
  110. got, err := Get(path)
  111. if err != nil {
  112. t.Fatalf("Get after Set(%d): %v", stars, err)
  113. }
  114. if got != stars {
  115. t.Errorf("Get after Set(%d) = %d, want %d", stars, got, stars)
  116. }
  117. }
  118. // Unrate (stars=0) must remove POPM
  119. if err := Set(path, 0); err != nil {
  120. t.Fatalf("Set(0): %v", err)
  121. }
  122. got, err := Get(path)
  123. if err != nil {
  124. t.Fatalf("Get after Set(0): %v", err)
  125. }
  126. if got != 0 {
  127. t.Errorf("Get after Set(0) = %d, want 0", got)
  128. }
  129. // Audio data must be intact after all the Set calls
  130. data, err := os.ReadFile(path)
  131. if err != nil {
  132. t.Fatalf("ReadFile: %v", err)
  133. }
  134. audioStart, _ := id3v2AudioStart(path)
  135. if audioStart >= int64(len(data)) {
  136. t.Fatalf("audioStart %d >= fileSize %d", audioStart, len(data))
  137. }
  138. got4096 := data[audioStart:]
  139. orig4096 := original[10:] // original audio starts at byte 10 (tag size 0)
  140. if len(got4096) < len(orig4096) {
  141. t.Errorf("audio data truncated: got %d bytes, want %d", len(got4096), len(orig4096))
  142. } else {
  143. // Last 4096 bytes should match original audio
  144. tail := got4096[len(got4096)-len(orig4096):]
  145. if !bytes.Equal(tail, orig4096) {
  146. t.Error("audio data corrupted after rating Set calls")
  147. }
  148. }
  149. }
  150. func TestSet_invalidStars(t *testing.T) {
  151. path := writeTempMP3(t, buildMinimalMP3())
  152. if err := Set(path, 6); err == nil {
  153. t.Error("Set(6) should return error")
  154. }
  155. if err := Set(path, -1); err == nil {
  156. t.Error("Set(-1) should return error")
  157. }
  158. }
  159. func TestSet_notMP3(t *testing.T) {
  160. f, err := os.CreateTemp(t.TempDir(), "test-*.flac")
  161. if err != nil {
  162. t.Fatal(err)
  163. }
  164. f.Close()
  165. if err := Set(f.Name(), 3); err == nil {
  166. t.Error("Set on non-MP3 should return error")
  167. }
  168. }