Move the package into src/
Thirty-seven entries in the root, most of them .go files. The assets had to come along: //go:embed cannot reach outside its own directory, so templates/, static/ and migrations/ live beside the code that embeds them, and testdata/ beside the test that reads it. storage/ stays put — runtime data, not source. go build now needs -o. Without it the output would be named after the package directory and collide with src/ itself.
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Line breaks are the content here — LRC timestamps are per line — so cleanLyrics must not do what
|
||||
// clean() does to a title.
|
||||
func TestCleanLyrics(t *testing.T) {
|
||||
got := cleanLyrics(" [00:11.74] Rivi yksi\r\n[00:13.99] Rivi\x07 kaksi\r\n\n")
|
||||
want := "[00:11.74] Rivi yksi\n[00:13.99] Rivi kaksi"
|
||||
if got != want {
|
||||
t.Fatalf("cleanLyrics gave %q, want %q", got, want)
|
||||
}
|
||||
if n := len([]rune(cleanLyrics(strings.Repeat("a", maxLyrics+500)))); n != maxLyrics {
|
||||
t.Fatalf("truncated to %d runes, want %d", n, maxLyrics)
|
||||
}
|
||||
}
|
||||
|
||||
// Plain text must parse to nil: that is the signal to scroll continuously rather than highlight
|
||||
// lines on timings nobody measured.
|
||||
func TestParseLRC(t *testing.T) {
|
||||
if got := parseLRC("Ihan tavallista tekstiä\ntoinen rivi"); got != nil {
|
||||
t.Fatalf("plain text parsed as synced: %v", got)
|
||||
}
|
||||
|
||||
lines := parseLRC("[00:11.74] Ensimmäinen\n[01:02] Toinen\n[00:05.5] Aikaisempi\nrivi ilman aikaa")
|
||||
if len(lines) != 3 {
|
||||
t.Fatalf("got %d lines, want 3 — untimed lines are dropped", len(lines))
|
||||
}
|
||||
// Sorted by time, whatever order the file had.
|
||||
if lines[0].At != 5.5 || lines[0].Text != "Aikaisempi" {
|
||||
t.Fatalf("first line is %+v, want 5.5s Aikaisempi", lines[0])
|
||||
}
|
||||
if lines[1].At != 11.74 || lines[2].At != 62 {
|
||||
t.Fatalf("timestamps parsed as %v and %v, want 11.74 and 62", lines[1].At, lines[2].At)
|
||||
}
|
||||
|
||||
// A refrain can carry several timestamps on one line, and each is its own occurrence.
|
||||
rep := parseLRC("[00:10.00][01:10.00] Kertosäe")
|
||||
if len(rep) != 2 || rep[0].At != 10 || rep[1].At != 70 {
|
||||
t.Fatalf("repeated stamps gave %+v, want two occurrences", rep)
|
||||
}
|
||||
}
|
||||
|
||||
// The lookup is a suggestion, so "nothing found" is a normal answer rather than an error, and a
|
||||
// synced hit always beats a plain one.
|
||||
func TestFetchLyrics(t *testing.T) {
|
||||
var lastPath string
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
lastPath = r.URL.Path
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
switch {
|
||||
case r.URL.Path == "/get" && r.URL.Query().Get("track_name") == "Paranoid":
|
||||
w.Write([]byte(`{"trackName":"Paranoid","artistName":"Black Sabbath","duration":168,
|
||||
"plainLyrics":"plain version","syncedLyrics":"[00:11.74] synced version"}`))
|
||||
case r.URL.Path == "/get":
|
||||
http.Error(w, `{"code":404}`, http.StatusNotFound)
|
||||
case r.URL.Path == "/search" && strings.Contains(r.URL.Query().Get("q"), "Soittorasia"):
|
||||
// An instrumental and a wrong-length take come first: both must be skipped.
|
||||
w.Write([]byte(`[{"trackName":"Soittorasia","duration":200,"instrumental":true,
|
||||
"plainLyrics":"","syncedLyrics":"[00:01.00] should be skipped"},
|
||||
{"trackName":"Soittorasia","duration":600,
|
||||
"plainLyrics":"wrong length take"},
|
||||
{"trackName":"Soittorasia","duration":201,
|
||||
"plainLyrics":"right one"}]`))
|
||||
default:
|
||||
w.Write([]byte(`[]`))
|
||||
}
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
old := lrclibBase
|
||||
lrclibBase = srv.URL
|
||||
defer func() { lrclibBase = old }()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
got, err := fetchLyrics(ctx, "Paranoid", "Black Sabbath", 168)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != "[00:11.74] synced version" {
|
||||
t.Fatalf("exact match returned %q, want the synced version", got)
|
||||
}
|
||||
|
||||
got, err = fetchLyrics(ctx, "Soittorasia", "Joku", 200)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != "right one" {
|
||||
t.Fatalf("search fallback returned %q — instrumental and wrong-length takes must be skipped", got)
|
||||
}
|
||||
if lastPath != "/search" {
|
||||
t.Fatalf("last request was %s, want the search fallback", lastPath)
|
||||
}
|
||||
|
||||
// Nothing found is not an error: the submitter simply types their own.
|
||||
got, err = fetchLyrics(ctx, "Ei olemassa", "Kukaan", 100)
|
||||
if err != nil || got != "" {
|
||||
t.Fatalf("miss returned %q, %v — want empty and no error", got, err)
|
||||
}
|
||||
|
||||
// No title means nothing to match on, and no request at all.
|
||||
if got, err := fetchLyrics(ctx, "", "Artisti", 100); err != nil || got != "" {
|
||||
t.Fatalf("empty title returned %q, %v", got, err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user